diff options
| author | hukl <contact@smyck.org> | 2009-04-28 00:15:53 +0200 |
|---|---|---|
| committer | hukl <contact@smyck.org> | 2009-05-01 17:14:02 +0200 |
| commit | 4bd16f053847f2efe347ebda9136ef2233ee0d2c (patch) | |
| tree | f4c11f89455de991c8d87726d5757b752e7129e2 /vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index | |
| parent | d3a9b46ba5c863a0ff377dcffae9a494fe476e02 (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.rb | 264 | ||||
| -rw-r--r-- | vendor/plugins/thinking-sphinx/lib/thinking_sphinx/index/faux_column.rb | 110 |
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 @@ | |||
| 1 | module 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 | ||
| 264 | end | ||
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 @@ | |||
| 1 | module 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 | ||
| 110 | end \ No newline at end of file | ||
