summaryrefslogtreecommitdiff
path: root/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index
diff options
context:
space:
mode:
authorhukl <contact@smyck.org>2009-04-28 00:15:53 +0200
committerhukl <contact@smyck.org>2009-05-01 17:14:02 +0200
commit4bd16f053847f2efe347ebda9136ef2233ee0d2c (patch)
treef4c11f89455de991c8d87726d5757b752e7129e2 /vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index
parentd3a9b46ba5c863a0ff377dcffae9a494fe476e02 (diff)
added thinking_sphinx plugin for fulltext search on nodes and heads
Diffstat (limited to 'vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index')
-rw-r--r--vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb264
-rw-r--r--vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb110
2 files changed, 374 insertions, 0 deletions
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb
new file mode 100644
index 0000000..dbd2ba0
--- /dev/null
+++ b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/builder.rb
@@ -0,0 +1,264 @@
1module ThinkingSphinx
2 class Index
3 # The Builder class is the core for the index definition block processing.
4 # There are four methods you really need to pay attention to:
5 # - indexes (aliased to includes and attribute)
6 # - has (aliased to attribute)
7 # - where
8 # - set_property (aliased to set_properties)
9 #
10 # The first two of these methods allow you to define what data makes up
11 # your indexes. #where provides a method to add manual SQL conditions, and
12 # set_property allows you to set some settings on a per-index basis. Check
13 # out each method's documentation for better ideas of usage.
14 #
15 class Builder
16 class << self
17 # No idea where this is coming from - haven't found it in any ruby or
18 # rails documentation. It's not needed though, so it gets undef'd.
19 # Hopefully the list of methods that get in the way doesn't get too
20 # long.
21 HiddenMethods = [:parent, :name, :id, :type].each { |method|
22 define_method(method) {
23 caller.grep(/irb.completion/).empty? ? method_missing(method) : super
24 }
25 }
26
27 attr_accessor :fields, :attributes, :properties, :conditions,
28 :groupings
29
30 # Set up all the collections. Consider this the equivalent of an
31 # instance's initialize method.
32 #
33 def setup
34 @fields = []
35 @attributes = []
36 @properties = {}
37 @conditions = []
38 @groupings = []
39 end
40
41 # This is how you add fields - the strings Sphinx looks at - to your
42 # index. Technically, to use this method, you need to pass in some
43 # columns and options - but there's some neat method_missing stuff
44 # happening, so lets stick to the expected syntax within a define_index
45 # block.
46 #
47 # Expected options are :as, which points to a column alias in symbol
48 # form, and :sortable, which indicates whether you want to sort by this
49 # field.
50 #
51 # Adding Single-Column Fields:
52 #
53 # You can use symbols or methods - and can chain methods together to
54 # get access down the associations tree.
55 #
56 # indexes :id, :as => :my_id
57 # indexes :name, :sortable => true
58 # indexes first_name, last_name, :sortable => true
59 # indexes users.posts.content, :as => :post_content
60 # indexes users(:id), :as => :user_ids
61 #
62 # Keep in mind that if any keywords for Ruby methods - such as id or
63 # name - clash with your column names, you need to use the symbol
64 # version (see the first, second and last examples above).
65 #
66 # If you specify multiple columns (example #2), a field will be created
67 # for each. Don't use the :as option in this case. If you want to merge
68 # those columns together, continue reading.
69 #
70 # Adding Multi-Column Fields:
71 #
72 # indexes [first_name, last_name], :as => :name
73 # indexes [location, parent.location], :as => :location
74 #
75 # To combine multiple columns into a single field, you need to wrap
76 # them in an Array, as shown by the above examples. There's no
77 # limitations on whether they're symbols or methods or what level of
78 # associations they come from.
79 #
80 # Adding SQL Fragment Fields
81 #
82 # You can also define a field using an SQL fragment, useful for when
83 # you would like to index a calculated value.
84 #
85 # indexes "age < 18", :as => :minor
86 #
87 def indexes(*args)
88 options = args.extract_options!
89 args.each do |columns|
90 field = Field.new(FauxColumn.coerce(columns), options)
91 fields << field
92
93 add_sort_attribute field, options if field.sortable
94 add_facet_attribute field, options if field.faceted
95 end
96 end
97 alias_method :field, :indexes
98 alias_method :includes, :indexes
99
100 # This is the method to add attributes to your index (hence why it is
101 # aliased as 'attribute'). The syntax is the same as #indexes, so use
102 # that as starting point, but keep in mind the following points.
103 #
104 # An attribute can have an alias (the :as option), but it is always
105 # sortable - so you don't need to explicitly request that. You _can_
106 # specify the data type of the attribute (the :type option), but the
107 # code's pretty good at figuring that out itself from peering into the
108 # database.
109 #
110 # Attributes are limited to the following types: integers, floats,
111 # datetimes (converted to timestamps), booleans and strings. Don't
112 # forget that Sphinx converts string attributes to integers, which are
113 # useful for sorting, but that's about it.
114 #
115 # You can also have a collection of integers for multi-value attributes
116 # (MVAs). Generally these would be through a has_many relationship,
117 # like in this example:
118 #
119 # has posts(:id), :as => :post_ids
120 #
121 # This allows you to filter on any of the values tied to a specific
122 # record. Might be best to read through the Sphinx documentation to get
123 # a better idea of that though.
124 #
125 # Adding SQL Fragment Attributes
126 #
127 # You can also define an attribute using an SQL fragment, useful for
128 # when you would like to index a calculated value. Don't forget to set
129 # the type of the attribute though:
130 #
131 # has "age < 18", :as => :minor, :type => :boolean
132 #
133 # If you're creating attributes for latitude and longitude, don't
134 # forget that Sphinx expects these values to be in radians.
135 #
136 def has(*args)
137 options = args.extract_options!
138 args.each do |columns|
139 attribute = Attribute.new(FauxColumn.coerce(columns), options)
140 attributes << attribute
141
142 add_facet_attribute attribute, options if attribute.faceted
143 end
144 end
145 alias_method :attribute, :has
146
147 def facet(*args)
148 options = args.extract_options!
149 options[:facet] = true
150
151 args.each do |columns|
152 attribute = Attribute.new(FauxColumn.coerce(columns), options)
153 attributes << attribute
154
155 add_facet_attribute attribute, options
156 end
157 end
158
159 # Use this method to add some manual SQL conditions for your index
160 # request. You can pass in as many strings as you like, they'll get
161 # joined together with ANDs later on.
162 #
163 # where "user_id = 10"
164 # where "parent_type = 'Article'", "created_at < NOW()"
165 #
166 def where(*args)
167 @conditions += args
168 end
169
170 # Use this method to add some manual SQL strings to the GROUP BY
171 # clause. You can pass in as many strings as you'd like, they'll get
172 # joined together with commas later on.
173 #
174 # group_by "lat", "lng"
175 #
176 def group_by(*args)
177 @groupings += args
178 end
179
180 # This is what to use to set properties on the index. Chief amongst
181 # those is the delta property - to allow automatic updates to your
182 # indexes as new models are added and edited - but also you can
183 # define search-related properties which will be the defaults for all
184 # searches on the model.
185 #
186 # set_property :delta => true
187 # set_property :field_weights => {"name" => 100}
188 # set_property :order => "name ASC"
189 # set_property :include => :picture
190 # set_property :select => 'name'
191 #
192 # Also, the following two properties are particularly relevant for
193 # geo-location searching - latitude_attr and longitude_attr. If your
194 # attributes for these two values are named something other than
195 # lat/latitude or lon/long/longitude, you can dictate what they are
196 # when defining the index, so you don't need to specify them for every
197 # geo-related search.
198 #
199 # set_property :latitude_attr => "lt", :longitude_attr => "lg"
200 #
201 # Please don't forget to add a boolean field named 'delta' to your
202 # model's database table if enabling the delta index for it.
203 # Valid options for the delta property are:
204 #
205 # true
206 # false
207 # :default
208 # :delayed
209 # :datetime
210 #
211 # You can also extend ThinkingSphinx::Deltas::DefaultDelta to implement
212 # your own handling for delta indexing.
213
214 def set_property(*args)
215 options = args.extract_options!
216 if options.empty?
217 @properties[args[0]] = args[1]
218 else
219 @properties.merge!(options)
220 end
221 end
222 alias_method :set_properties, :set_property
223
224 # Handles the generation of new columns for the field and attribute
225 # definitions.
226 #
227 def method_missing(method, *args)
228 FauxColumn.new(method, *args)
229 end
230
231 # A method to allow adding fields from associations which have names
232 # that clash with method names in the Builder class (ie: properties,
233 # fields, attributes).
234 #
235 # Example: indexes assoc(:properties).column
236 #
237 def assoc(assoc, *args)
238 FauxColumn.new(assoc, *args)
239 end
240
241 private
242
243 def add_sort_attribute(field, options)
244 add_internal_attribute field, options, "_sort"
245 end
246
247 def add_facet_attribute(resource, options)
248 add_internal_attribute resource, options, "_facet", true
249 end
250
251 def add_internal_attribute(resource, options, suffix, crc = false)
252 @attributes << Attribute.new(
253 resource.columns.collect { |col| col.clone },
254 options.merge(
255 :type => resource.is_a?(Field) ? :string : nil,
256 :as => resource.unique_name.to_s.concat(suffix).to_sym,
257 :crc => crc
258 ).except(:facet)
259 )
260 end
261 end
262 end
263 end
264end
diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb
new file mode 100644
index 0000000..84068de
--- /dev/null
+++ b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb
@@ -0,0 +1,110 @@
1module ThinkingSphinx
2 class Index
3 # Instances of this class represent database columns and the stack of
4 # associations that lead from the base model to them.
5 #
6 # The name and stack are accessible through methods starting with __ to
7 # avoid conflicting with the method_missing calls that build the stack.
8 #
9 class FauxColumn
10 # Create a new column with a pre-defined stack. The top element in the
11 # stack will get shifted to be the name value.
12 #
13 def initialize(*stack)
14 @name = stack.pop
15 @stack = stack
16 end
17
18 def self.coerce(columns)
19 case columns
20 when Symbol, String
21 FauxColumn.new(columns)
22 when Array
23 columns.collect { |col| FauxColumn.coerce(col) }
24 when FauxColumn
25 columns
26 else
27 nil
28 end
29 end
30
31 # Can't use normal method name, as that could be an association or
32 # column name.
33 #
34 def __name
35 @name
36 end
37
38 # Can't use normal method name, as that could be an association or
39 # column name.
40 #
41 def __stack
42 @stack
43 end
44
45 # Returns true if the stack is empty *and* if the name is a string -
46 # which is an indication that of raw SQL, as opposed to a value from a
47 # table's column.
48 #
49 def is_string?
50 @name.is_a?(String) && @stack.empty?
51 end
52
53 # This handles any 'invalid' method calls and sets them as the name,
54 # and pushing the previous name into the stack. The object returns
55 # itself.
56 #
57 # If there's a single argument, it becomes the name, and the method
58 # symbol goes into the stack as well. Multiple arguments means new
59 # columns with the original stack and new names (from each argument) gets
60 # returned.
61 #
62 # Easier to explain with examples:
63 #
64 # col = FauxColumn.new :a, :b, :c
65 # col.__name #=> :c
66 # col.__stack #=> [:a, :b]
67 #
68 # col.whatever #=> col
69 # col.__name #=> :whatever
70 # col.__stack #=> [:a, :b, :c]
71 #
72 # col.something(:id) #=> col
73 # col.__name #=> :id
74 # col.__stack #=> [:a, :b, :c, :whatever, :something]
75 #
76 # cols = col.short(:x, :y, :z)
77 # cols[0].__name #=> :x
78 # cols[0].__stack #=> [:a, :b, :c, :whatever, :something, :short]
79 # cols[1].__name #=> :y
80 # cols[1].__stack #=> [:a, :b, :c, :whatever, :something, :short]
81 # cols[2].__name #=> :z
82 # cols[2].__stack #=> [:a, :b, :c, :whatever, :something, :short]
83 #
84 # Also, this allows method chaining to build up a relevant stack:
85 #
86 # col = FauxColumn.new :a, :b
87 # col.__name #=> :b
88 # col.__stack #=> [:a]
89 #
90 # col.one.two.three #=> col
91 # col.__name #=> :three
92 # col.__stack #=> [:a, :b, :one, :two]
93 #
94 def method_missing(method, *args)
95 @stack << @name
96 @name = method
97
98 if (args.empty?)
99 self
100 elsif (args.length == 1)
101 method_missing(args.first)
102 else
103 args.collect { |arg|
104 FauxColumn.new(@stack + [@name, arg])
105 }
106 end
107 end
108 end
109 end
110end \ No newline at end of file