sphinxsearchlogic 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ == 0.9.1 released 2009-08-25
2
+
3
+ * Bumped gem version and add Rails init stuff.
4
+
5
+ == 0.9.0 released 2009-08-25
6
+
7
+ * First release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
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.rdoc ADDED
@@ -0,0 +1,161 @@
1
+ = Sphinxsearchlogic
2
+
3
+ Sphinxsearchlogic is for ThinkingSphinx what Searchlogic is for ActiveRecord.. or at least something similar.
4
+
5
+ == Helpful links
6
+
7
+ * <b>Github:</b> http://github.com/joost/sphinxsearchlogic
8
+
9
+ Sphinxsearchlogic is largely based on / using:
10
+
11
+ * <b>Searchlogic:</b> http://github.com/binarylogic/searchlogic/
12
+ * <b>ThinkingSphinx:</b> http://freelancing-god.github.com/ts/en/
13
+ * <b>Sphinx:</b> http://www.sphinxsearch.com/
14
+
15
+ If you're not familiar with ThinkingSphinx check {this presentation!}[http://www.slideshare.net/freelancing_god/solving-the-riddle-of-search-using-sphinx-with-rails-1406954]
16
+
17
+ == Install
18
+
19
+ First you need Sphinx and ThinkingSphinx. Simply because they rock. Check the ThinkingSphinx pages for this.
20
+ Use the gem in your environment.rb like:
21
+
22
+ config.gem "freelancing-god-thinking-sphinx", :lib => 'thinking_sphinx', :version => '1.2.7'
23
+
24
+ Install as gem from Github (recommended).
25
+
26
+ gem sources -a http://gems.github.com (you only have to do this once)
27
+ sudo gem install joost-sphinxsearchlogic
28
+
29
+ Next use it in your environment.rb like:
30
+
31
+ config.gem 'joost-sphinxsearchlogic', :lib => 'sphinxsearchlogic'
32
+
33
+ Install as plugin from Github.
34
+
35
+ ./script/plugin install git://github.com/joost/sphinxsearchlogic.git
36
+
37
+ == Usage
38
+
39
+ Use Sphinxsearchlogic as you use the Searchlogic search method:
40
+
41
+ @search = Movie.sphinxsearchlogic(params[:search])
42
+
43
+ The search params you can pass:
44
+
45
+ Search:
46
+
47
+ :all => 'something' # search('something')
48
+ :name => 'john' # search(:conditions => {:name => 'john'})
49
+
50
+ Filters:
51
+
52
+ :with_age => 20 # search(:with => {:age => 20})
53
+ :with_age => [21, 22] # search(:with => {:age => [21, 22]})
54
+ :with_age => 20..25 # search(:with => {:age => 20..25})
55
+
56
+ :without_age => 20 # search(:without => {:age => 20})
57
+
58
+ For MVAs you can also use:
59
+
60
+ :with_all_tags => [1,2,3] # search(:with_all => {:tags => [1,2,3]})
61
+
62
+ Thinking Sphinx scopes:
63
+ :my_scope => true # my_scope (actually called with my_scope(true))
64
+ :some_scope => 'sweet' # some_scope(sweet)
65
+
66
+ === Ordering
67
+
68
+ Ordering is implemented similar to Searchlogic.
69
+
70
+ :order => 'ascend_by_created_at' # :order => :attribute,
71
+ :order => 'descend_by_created_at' # :order => :attribute, :sort_mode => :desc
72
+ More advanced ordering? Use scopes! Like for {:order => 'rating DESC, votes DESC'} or {:sort_mode => :expr, :sort_by => '@weight * ranking'}
73
+ :order => 'my_order_scope'
74
+
75
+ For your views see the order helper below.
76
+
77
+ === Pagination
78
+
79
+ Unsimilar to Searchlogic Sphinxsearchlogic does pagination in the search.
80
+ You can add them as follows since all arguments are merged.
81
+
82
+ @search = Movie.sphinxsearchlogic(params[:search], :page => params[:page], :per_page => params[:per_page])
83
+
84
+ If not specified default limits and pagination is used. As pagination is 'Always on' with ThinkingSphinx.
85
+
86
+ == Examples
87
+
88
+ === Your controller
89
+
90
+ An example controller action:
91
+
92
+ class MovieController < ApplicationController
93
+ def index
94
+ @search = Movie.sphinxsearchlogic(params[:search], :page => params[:page], :per_page => params[:per_page])
95
+ @movies = @sphinxsearch.results
96
+ end
97
+ end
98
+
99
+ === Your search forms
100
+
101
+ An example view search form:
102
+
103
+ <% sphinxsearchlogic_form_for @search do |form| %>
104
+ <p>
105
+ <%= form.label :all %>
106
+ <%= form.text_field :all %>
107
+ </p>
108
+ <p>
109
+ <%= form.check_box :scary_movies, {}, '1', nil %>
110
+ Only scary movies
111
+ </p>
112
+ <% end %>
113
+
114
+ The first field will send search[:all] params which will fulltext search through your data.
115
+ The second is making use of a ThinkingSphinx scope (http://freelancing-god.github.com/ts/en/scopes.html) so it
116
+ only works if you've defined it in your model.
117
+
118
+ === Helpers
119
+
120
+ You can use a similar order helper as Searchlogic offers. You can order by attributes and fields (only if they
121
+ are specified as sortable in your ThinkingSphinx index).
122
+
123
+ <%= order(@search, :by => :title, :as => 'Movie Title')
124
+
125
+ When you create two ThinkingSphinx scopes in your model you can even do special exotic ordering.
126
+
127
+ sphinx_scope(:ascend_by_rating_and_votes) {
128
+ {:order => "rating ASC, votes ASC"}
129
+ }
130
+
131
+ sphinx_scope(:descend_by_rating_and_votes) {
132
+ {:order => "rating DESC, votes DESC"}
133
+ }
134
+
135
+ You can also use this in the helper:
136
+
137
+ <%= order(@search, :by => :rating_and_votes, :as => 'Special ordering')
138
+
139
+ == TODO
140
+
141
+ Things that might be in next versions. Please contact me via Github if you've any suggestions or want to
142
+ contribute.
143
+
144
+ === Sanitize
145
+
146
+ Sanitize params so we don't f*ck with ThinkingSphinx.
147
+
148
+ === Facets
149
+
150
+ Easy facets (http://freelancing-god.github.com/ts/en/facets.html) support.
151
+
152
+ === Defaults
153
+
154
+ On the Model you want to search specify the defaults for the search. Eg. on the Movie model:
155
+
156
+ sphinxsearchlogic_default_order = 'descend_by_weight'
157
+ sphinxsearchlogic_protected = :order, :per_page, :age, :name, :match_mode
158
+ sphinxsearchlogic_max_per_page = 100
159
+ sphinxsearchlogic_match_mode = :any
160
+
161
+ Copyright (c) 2009 Joost Hietbrink, released under the MIT license
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 9
4
+ :patch: 1
data/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'sphinxsearchlogic'
2
+ ActiveRecord::Base.extend(Sphinxsearchlogic::Search::Implementation)
3
+
4
+ if defined?(ActionController)
5
+ require "rails_helpers"
6
+ ActionController::Base.helper(Sphinxsearchlogic::RailsHelpers)
7
+ end
@@ -0,0 +1,45 @@
1
+ module Sphinxsearchlogic
2
+ module RailsHelpers
3
+
4
+ # Creates a form with a :search scope. Use to create search form in your views.
5
+ def sphinxsearchlogic_form_for(*args, &block)
6
+ if search_obj = args.find { |arg| arg.is_a?(Sphinxsearchlogic::Search) }
7
+ options = args.extract_options!
8
+ options[:html] ||= {}
9
+ options[:html][:method] ||= :get
10
+ options[:url] ||= url_for
11
+ args.unshift(:search) if args.first == search_obj
12
+ args << options
13
+ end
14
+ form_for(*args, &block)
15
+ end
16
+
17
+ # Similar to the Searchlogic order helper.
18
+ def order(search, options = {}, html_options = {})
19
+ options[:params_scope] ||= :search
20
+ options[:as] ||= options[:by].to_s.humanize
21
+ options[:ascend_scope] ||= "ascend_by_#{options[:by]}"
22
+ options[:descend_scope] ||= "descend_by_#{options[:by]}"
23
+ ascending = search.order.to_s == options[:ascend_scope]
24
+ new_scope = ascending ? options[:descend_scope] : options[:ascend_scope]
25
+ selected = [options[:ascend_scope], options[:descend_scope]].include?(search.order.to_s)
26
+ if selected
27
+ css_classes = html_options[:class] ? html_options[:class].split(" ") : []
28
+ if ascending
29
+ options[:as] = "&#9650;&nbsp;#{options[:as]}"
30
+ css_classes << "ascending"
31
+ else
32
+ options[:as] = "&#9660;&nbsp;#{options[:as]}"
33
+ css_classes << "descending"
34
+ end
35
+ html_options[:class] = css_classes.join(" ")
36
+ end
37
+ params = controller.params.clone
38
+ params[options[:params_scope]] ||= {}
39
+ params[options[:params_scope]].merge!(:order => new_scope)
40
+ url_options = {:controller => params[:controller], :action => params[:action], options[:params_scope] => params[options[:params_scope]]}
41
+ link_to(options[:as], url_options, html_options)
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,322 @@
1
+ # Sphinxsearchlogic
2
+ #
3
+ # A adapted version of the Searchlogic::Search class to attach to an ActiveRecord::Base#sphinxsearchlogic
4
+ # method.
5
+ module Sphinxsearchlogic
6
+ class Search
7
+ module Implementation
8
+ # Use like:
9
+ # Movie.sphinxsearchlogic(params[:search], :page => params[:page], :per_page => [:per_page])
10
+ def sphinxsearchlogic(conditions = {}, pagination = {})
11
+ conditions ||= {} # params[:search] might be nil
12
+ conditions.merge!(pagination)
13
+ # Merge array of hashes, but doesn't work if Hash value is an Array.
14
+ # conditions = Hash[*args.collect {|h| h.to_a}.flatten]
15
+ Search.new(self, scope(:find), conditions)
16
+ end
17
+ end
18
+
19
+ # Is an invalid condition is used this error will be raised. Ex:
20
+ #
21
+ # User.search(:unkown => true)
22
+ #
23
+ # Where unknown is not a valid named scope for the User model.
24
+ class UnknownConditionError < StandardError
25
+ def initialize(condition)
26
+ msg = "The #{condition} is not a valid condition. You may only use conditions that map to a thinking sphinx named scope or attribute."
27
+ super(msg)
28
+ end
29
+ end
30
+
31
+ # class OutofboundsError < StandardError
32
+ # def initialize(page, per_page)
33
+ # msg = "The page #{page} is out of bounds. Using per_page #{per_page}."
34
+ # super(msg)
35
+ # end
36
+ # end
37
+
38
+ # Accessors that define this Search object.
39
+ attr_accessor :klass, :current_scope, :with, :without, :with_all, :params, :conditions, :scopes, :all
40
+ undef :id if respond_to?(:id)
41
+
42
+ module Pagination
43
+ attr_writer :page, :per_page, :max_matches
44
+
45
+ def default_per_page
46
+ default_per_page = 20
47
+ default_per_page = klass.per_page if klass.respond_to?(:per_page)
48
+ default_per_page
49
+ end
50
+
51
+ # Returns a set max_matches or 1000.
52
+ def max_matches
53
+ @max_matches || 1000
54
+ end
55
+
56
+ # Returns the last page we have in this search based on the max_matches.
57
+ # So we don't get a Riddle error for an offset that is too high.
58
+ def last_page
59
+ (max_matches.to_f / per_page).ceil
60
+ end
61
+
62
+ def offset
63
+ (page-1)*per_page
64
+ end
65
+
66
+ def page
67
+ page = (@page || 1).to_i
68
+ page = 1 if page < 1 # Fixes pages like -1 and 0.
69
+ # Fix riddle error.. we always return the last page? Think should be handled by application!
70
+ # However this isn't yet the case for pages > total_results.
71
+ # raise OutofboundsError.new(page, per_page) if page > last_page
72
+ page = last_page if page > last_page
73
+ page
74
+ end
75
+
76
+ # Returns 20 by default (ThinkingSphinx/Riddle default)
77
+ def per_page
78
+ per_page = (@per_page || default_per_page).to_i
79
+ per_page = default_per_page if per_page < 1 # Fixes per_page like -1 and 0.
80
+ per_page
81
+ end
82
+
83
+ def pagination_options
84
+ options = {}
85
+ options[:page] = page
86
+ options[:per_page] = per_page
87
+ options
88
+ end
89
+
90
+ end
91
+ include Pagination
92
+
93
+ module Ordering
94
+
95
+ attr_reader :order, :order_direction, :order_attribute
96
+
97
+ # Sets the order. If the order is incorrect this won't be set. Similar to pagination the
98
+ # defaults will be used.
99
+ def order=(order)
100
+ @order = order
101
+ return if order.blank?
102
+ if is_sphinx_scope?(order) # We first check for scopes since they might be named ascend_by_scopename.
103
+ scopes[order.to_sym] = true
104
+ elsif order.to_s =~ /^(ascend|descend)_by_(\w+)$/
105
+ @order_direction = ($1 == 'ascend') ? :asc : :desc
106
+ if is_sphinx_attribute?($2)
107
+ @order_attribute = $2.to_sym
108
+ elsif [:weight, :relevance, :rank, :id, :random, :geodist].include?($2.to_sym)
109
+ @order_attribute = "@#{$2} #{@order_direction}"
110
+ end
111
+ end
112
+ end
113
+
114
+ def ordering_options
115
+ if order_attribute.blank?
116
+ {}
117
+ elsif order_attribute.is_a?(Symbol)
118
+ {
119
+ :order => order_attribute,
120
+ :sort_mode => order_direction
121
+ }
122
+ else
123
+ {:order => order_attribute}
124
+ end
125
+ end
126
+
127
+ end
128
+ include Ordering
129
+
130
+ # Creates a new search object for the given class. Ex:
131
+ #
132
+ # Searchlogic::Search.new(User, {}, {:username_like => "bjohnson"})
133
+ def initialize(klass, current_scope, params = {})
134
+ @with = {}
135
+ @without = {}
136
+ @with_all = {}
137
+ @conditions = {}
138
+ @scopes = {}
139
+
140
+ self.klass = klass
141
+ raise "No Sphinx indexes found on #{klass.to_s}!" unless has_sphinx_index?
142
+ self.current_scope = current_scope
143
+ self.params = params if params.is_a?(Hash)
144
+ end
145
+
146
+ # Accepts a hash of conditions.
147
+ def params=(values)
148
+ values.each do |param, value|
149
+ value.delete_if { |v| v.blank? } if value.is_a?(Array)
150
+ next if value.blank?
151
+ send("#{param}=", value)
152
+ end
153
+ end
154
+
155
+ # Returns actual search results.
156
+ # Movie.sphinxsearchlogic.results
157
+ def results
158
+ Rails.logger.debug("Sphinxsearchlogic: #{klass.to_s}.search('#{all}', #{search_options.inspect})")
159
+ if scopes.empty?
160
+ klass.search(all, search_options)
161
+ else
162
+ cloned_scopes = scopes.clone # Clone scopes since we're deleting form the hash.
163
+ # Get the first scope and call all others on this one..
164
+ first_scope = cloned_scopes.keys.first
165
+ first_args = cloned_scopes.delete(first_scope)
166
+ result = klass.send(first_scope, first_args)
167
+ # Call remaining scopes on this scope.
168
+ cloned_scopes.each do |scope, args|
169
+ result = result.send(scope, args)
170
+ end
171
+ result.search(all, search_options)
172
+ end
173
+ end
174
+
175
+ # private
176
+
177
+ # Handles (in order):
178
+ # * with / without / with_all conditions (Filters)
179
+ # * field conditions (Regular searches)
180
+ # * scope conditions
181
+ def method_missing(name, *args, &block)
182
+ name = name.to_s
183
+ if name =~ /^(\w+)=$/ # If we have a setter
184
+ name = $1
185
+ if name =~ /^with(out|_all)?_(\w+)$/
186
+ attribute_name = $2.to_sym
187
+ if is_sphinx_attribute?(attribute_name)
188
+ # Put in with / without / with_all depending on what the regexp matched.
189
+ if $1 == 'out'
190
+ without[attribute_name] = type_cast(args.first, cast_type(attribute_name))
191
+ elsif $1 == '_all'
192
+ with_all[attribute_name] = type_cast(args.first, cast_type(attribute_name))
193
+ else
194
+ with[attribute_name] = type_cast(args.first, cast_type(attribute_name))
195
+ end
196
+ else
197
+ raise UnknownConditionError.new(attribute_name)
198
+ end
199
+ elsif is_sphinx_field?(name)
200
+ conditions[name.to_sym] = args.first
201
+ elsif is_sphinx_scope?(name)
202
+ scopes[name.to_sym] = args.first
203
+ else
204
+ # If we have an unknown setter..
205
+ # raise UnknownConditionError.new(attribute_name)
206
+ super
207
+ end
208
+ else
209
+ if name =~ /^with(out|_all)?_(\w+)$/
210
+ attribute_name = $2.to_sym
211
+ if is_sphinx_attribute?(attribute_name)
212
+ # Put in with / without / with_all depending on what the regexp matched.
213
+ if $1 == 'out'
214
+ without[attribute_name]
215
+ elsif $1 == '_all'
216
+ with_all[attribute_name]
217
+ else
218
+ with[attribute_name]
219
+ end
220
+ else
221
+ raise UnknownConditionError.new(attribute_name)
222
+ end
223
+ elsif is_sphinx_field?(name)
224
+ conditions[name.to_sym]
225
+ elsif is_sphinx_scope?(name)
226
+ scopes[name.to_sym]
227
+ else
228
+ # If we have something else than a setter..
229
+ # raise UnknownConditionError.new(attribute_name)
230
+ super
231
+ end
232
+ end
233
+ end
234
+
235
+ # Returns a hash for the ThinkingSphinx search method. Eg.
236
+ # {
237
+ # :with => {:year => 2001}
238
+ # }
239
+ def attribute_filter_options
240
+ options = {}
241
+ options[:with] = with unless with.blank?
242
+ options[:without] = without unless without.blank?
243
+ options[:with_all] = with_all unless with_all.blank? # See http://www.mailinglistarchive.com/thinking-sphinx@googlegroups.com/msg00351.html
244
+ options
245
+ end
246
+
247
+ # Returns a hash for the ThinkingSphinx search method. Eg.
248
+ # {
249
+ # :conditions => {:name => 'John'}
250
+ # }
251
+ def search_options
252
+ options = {}
253
+ options[:conditions] = conditions unless conditions.blank?
254
+ options.merge(attribute_filter_options).merge(ordering_options).merge(pagination_options)
255
+ end
256
+
257
+ # # cleanup_hash removes empty and nil stuff from params hashes.
258
+ # def cleanup_hash(hash)
259
+ # hash.collect do |condition, value|
260
+ # value.delete_if { |v| v.blank? } if value.is_a?(Array)
261
+ # value unless value.blank?
262
+ # end.compact
263
+ # end
264
+
265
+ # Returns the ThinkingSphinx index for the klass we search on.
266
+ def sphinx_index
267
+ klass.sphinx_indexes.first
268
+ end
269
+
270
+ # Returns true if the class of this Search has a Sphinx index.
271
+ def has_sphinx_index?
272
+ sphinx_index.is_a?(ThinkingSphinx::Index)
273
+ rescue
274
+ false
275
+ end
276
+
277
+ # Returns particular ThinkingSphinx::Attribute.
278
+ def sphinx_attribute(attribute_name)
279
+ sphinx_index.attributes.find do |index_attribute|
280
+ index_attribute.public? && index_attribute.unique_name.to_s =~ /^#{attribute_name}(_sort)?$/ # Also check for :sortable attributes (they are given prefix _sort)
281
+ end
282
+ end
283
+
284
+ # Returns true if the class of this search has a public attribute with this name (or name_sort if field is :sortable).
285
+ def is_sphinx_attribute?(attribute_name)
286
+ !!sphinx_attribute(attribute_name)
287
+ end
288
+
289
+ # Returns true if the class of this search has a public field with this name.
290
+ def is_sphinx_field?(field_name)
291
+ !sphinx_index.fields.find do |index_field|
292
+ index_field.public? && (index_field.unique_name.to_s == field_name.to_s)
293
+ end.nil?
294
+ end
295
+
296
+ # Returns true if class of this search has a sphinx scope with this name.
297
+ def is_sphinx_scope?(scope_name)
298
+ klass.sphinx_scopes.include?(scope_name.to_sym)
299
+ end
300
+
301
+ # Returns the type we should type_cast a ThinkingSphinx::Attribute to, eg. :integer.
302
+ def cast_type(name)
303
+ sphinx_attribute(name).type
304
+ end
305
+
306
+ # type_cast method of Searchlogic plugin
307
+ def type_cast(value, type)
308
+ case value
309
+ when Array
310
+ value.collect { |v| type_cast(v, type) }
311
+ else
312
+ # Let's leverage ActiveRecord's type casting, so that casting is consistent
313
+ # with the other models.
314
+ column_for_type_cast = ::ActiveRecord::ConnectionAdapters::Column.new("", nil)
315
+ column_for_type_cast.instance_variable_set(:@type, type)
316
+ value = column_for_type_cast.type_cast(value)
317
+ Time.zone && value.is_a?(Time) ? value.in_time_zone : value
318
+ end
319
+ end
320
+
321
+ end
322
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'sphinxsearchlogic'
2
+ ActiveRecord::Base.extend(Sphinxsearchlogic::Search::Implementation)
3
+
4
+ if defined?(ActionController)
5
+ require "rails_helpers"
6
+ ActionController::Base.helper(Sphinxsearchlogic::RailsHelpers)
7
+ end
@@ -0,0 +1,159 @@
1
+ require 'test_helper'
2
+
3
+ # Define classes to test on..
4
+ class Book < ActiveRecord::Base
5
+
6
+ establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
7
+
8
+ define_index do
9
+ indexes title, :sortable => true
10
+ indexes description
11
+ has :production_year
12
+ end
13
+
14
+ sphinx_scope(:millenium) do
15
+ {:with => {:production_year => 2000..9999}}
16
+ end
17
+
18
+ end
19
+
20
+ class SphinxsearchlogicTest < ActiveSupport::TestCase
21
+
22
+ def setup
23
+ end
24
+
25
+ # General
26
+
27
+ test 'book model should have sphinx index' do
28
+ assert Book.sphinxsearchlogic.send(:has_sphinx_index?)
29
+ end
30
+
31
+ # Attribute Filters
32
+
33
+ test 'valid with params' do
34
+ search = Book.sphinxsearchlogic(:with_production_year => '2006')
35
+ assert_equal({:production_year => '2006'}, search.with)
36
+ end
37
+
38
+ test 'invalid with params' do
39
+ assert_raise Sphinxsearchlogic::Search::UnknownConditionError do
40
+ search = Book.sphinxsearchlogic(:with_non_existing_stuff => '2006')
41
+ end
42
+ end
43
+
44
+ test 'valid without params' do
45
+ search = Book.sphinxsearchlogic(:without_production_year => '2006')
46
+ assert_equal({:production_year => '2006'}, search.without)
47
+ end
48
+
49
+ test 'invalid without params' do
50
+ assert_raise Sphinxsearchlogic::Search::UnknownConditionError do
51
+ search = Book.sphinxsearchlogic(:without_non_existing_stuff => '2006')
52
+ end
53
+ end
54
+
55
+ # Regular field and scope searches
56
+
57
+ test 'empty search' do
58
+ search = Book.sphinxsearchlogic()
59
+ assert_equal({}, search.search_options)
60
+ end
61
+
62
+ test 'search on all' do
63
+ search = Book.sphinxsearchlogic(:all => 'test')
64
+ assert_equal('test', search.all)
65
+ end
66
+
67
+ test 'search on field' do
68
+ search = Book.sphinxsearchlogic(:title => 'test')
69
+ assert_equal('test', search.title)
70
+ assert_equal({:conditions => {:title => 'test'}}, search.search_options)
71
+ end
72
+
73
+ test 'scope search' do
74
+ search = Book.sphinxsearchlogic(:millenium => true)
75
+ assert_equal(true, search.millenium)
76
+ end
77
+
78
+ # Ordering
79
+
80
+ test 'invalid ordering by non existing scope' do
81
+ search = Book.sphinxsearchlogic(:order => 'non_existing_scope')
82
+ assert_equal({}, search.ordering_options)
83
+ end
84
+
85
+ test 'invalid ordering by non sortable field' do
86
+ search = Book.sphinxsearchlogic(:order => 'ascend_by_description')
87
+ assert_equal({}, search.ordering_options)
88
+ end
89
+
90
+ test 'ascend ordering by sortable field' do
91
+ search = Book.sphinxsearchlogic(:order => 'ascend_by_title')
92
+ assert_equal({:order => :title, :sort_mode => :asc}, search.ordering_options)
93
+ end
94
+
95
+ test 'descend ordering by sortable field' do
96
+ search = Book.sphinxsearchlogic(:order => 'descend_by_title')
97
+ assert_equal({:order => :title, :sort_mode => :desc}, search.ordering_options)
98
+ end
99
+
100
+ test 'ascend ordering by attribute' do
101
+ search = Book.sphinxsearchlogic(:order => 'ascend_by_production_year')
102
+ assert_equal({:order => :production_year, :sort_mode => :asc}, search.ordering_options)
103
+ end
104
+
105
+ test 'descend ordering by @relevance' do
106
+ search = Book.sphinxsearchlogic(:order => 'descend_by_relevance')
107
+ assert_equal({:order => '@relevance desc'}, search.ordering_options)
108
+ end
109
+
110
+ # Pagination
111
+
112
+ test 'pagination' do
113
+ search = Book.sphinxsearchlogic(:per_page => '123', :page => '12')
114
+ assert_equal(12, search.page)
115
+ assert_equal(123, search.per_page)
116
+ # We also check if the search_options are correctly set..
117
+ assert_equal(12, search.search_options[:page])
118
+ assert_equal(123, search.search_options[:per_page])
119
+ end
120
+
121
+ test 'invalid pagination' do
122
+ search = Book.sphinxsearchlogic(:per_page => 'asdasd', :page => 'as')
123
+ assert_equal(1, search.page)
124
+ assert_equal(10, search.per_page)
125
+
126
+ search = Book.sphinxsearchlogic(:per_page => '0', :page => '0')
127
+ assert_equal(1, search.page)
128
+ assert_equal(10, search.per_page)
129
+
130
+ search = Book.sphinxsearchlogic(:per_page => '-123', :page => '-12')
131
+ assert_equal(1, search.page)
132
+ assert_equal(10, search.per_page)
133
+ end
134
+
135
+ test 'no pagination' do
136
+ search = Book.sphinxsearchlogic(:per_page => '', :page => '')
137
+ assert_equal(nil, search.page)
138
+ assert_equal(nil, search.per_page)
139
+ end
140
+
141
+ # Other methods
142
+
143
+ test 'attribute finding' do
144
+ assert Book.sphinxsearchlogic.is_sphinx_attribute?('title') # sortable so attribute
145
+ assert !Book.sphinxsearchlogic.is_sphinx_attribute?('notitle') # not existing
146
+ assert !Book.sphinxsearchlogic.is_sphinx_attribute?('description') # attribute
147
+ end
148
+
149
+ test 'field finding' do
150
+ assert Book.sphinxsearchlogic.is_sphinx_field?('title') # but also field
151
+ assert Book.sphinxsearchlogic.is_sphinx_field?('description')
152
+ assert !Book.sphinxsearchlogic.is_sphinx_field?('production_year')
153
+ end
154
+
155
+ test 'scope finding' do
156
+ assert Book.sphinxsearchlogic.is_sphinx_scope?('millenium')
157
+ end
158
+
159
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'active_support/test_case'
4
+
5
+ # Create some test db stuff..
6
+ require 'activerecord'
7
+
8
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
9
+ ActiveRecord::Base.configurations = true
10
+
11
+ ActiveRecord::Schema.verbose = false
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+ create_table :books do |t|
14
+ t.string :title
15
+ t.string :description
16
+ t.integer :production_year
17
+ t.timestamps
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sphinxsearchlogic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ platform: ruby
6
+ authors:
7
+ - Joost Hietbrink
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-25 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ version:
25
+ description: Searchlogic provides common named scopes and object based searching for ActiveRecord.
26
+ email: joost@joopp.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - MIT-LICENSE
33
+ - README.rdoc
34
+ - CHANGELOG.rdoc
35
+ files:
36
+ - CHANGELOG.rdoc
37
+ - MIT-LICENSE
38
+ - README.rdoc
39
+ - VERSION.yml
40
+ - init.rb
41
+ - lib/sphinxsearchlogic.rb
42
+ - lib/rails_helpers.rb
43
+ - rails/init.rb
44
+ - test/sphinxsearchlogic_test.rb
45
+ - test/test_helper.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/joost/sphinxsearchlogic
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: sphinxsearchlogic
70
+ rubygems_version: 1.3.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Sphinxsearchlogic is for ThinkingSphinx what Searchlogic is for ActiveRecord.. or at least something similar.
74
+ test_files:
75
+ - test/sphinxsearchlogic_test.rb
76
+ - test/test_helper.rb