sphinxsearchlogic 0.9.1

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/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