From 4bd16f053847f2efe347ebda9136ef2233ee0d2c Mon Sep 17 00:00:00 2001 From: hukl Date: Tue, 28 Apr 2009 00:15:53 +0200 Subject: added thinking_sphinx plugin for fulltext search on nodes and heads --- .../thinking-sphinx/lib/thinking_sphinx/field.rb | 172 +++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb (limited to 'vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb') diff --git a/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb new file mode 100644 index 0000000..9edaede --- /dev/null +++ b/vendor/plugins/thinking-sphinx/lib/thinking_sphinx/field.rb @@ -0,0 +1,172 @@ +module ThinkingSphinx + # Fields - holding the string data which Sphinx indexes for your searches. + # This class isn't really useful to you unless you're hacking around with the + # internals of Thinking Sphinx - but hey, don't let that stop you. + # + # One key thing to remember - if you're using the field manually to + # generate SQL statements, you'll need to set the base model, and all the + # associations. Which can get messy. Use Index.link!, it really helps. + # + class Field + attr_accessor :alias, :columns, :sortable, :associations, :model, :infixes, + :prefixes, :faceted + + # To create a new field, you'll need to pass in either a single Column + # or an array of them, and some (optional) options. The columns are + # references to the data that will make up the field. + # + # Valid options are: + # - :as => :alias_name + # - :sortable => true + # - :infixes => true + # - :prefixes => true + # + # Alias is only required in three circumstances: when there's + # another attribute or field with the same name, when the column name is + # 'id', or when there's more than one column. + # + # Sortable defaults to false - but is quite useful when set to true, as + # it creates an attribute with the same string value (which Sphinx converts + # to an integer value), which can be sorted by. Thinking Sphinx is smart + # enough to realise that when you specify fields in sort statements, you + # mean their respective attributes. + # + # If you have partial matching enabled (ie: enable_star), then you can + # specify certain fields to have their prefixes and infixes indexed. Keep + # in mind, though, that Sphinx's default is _all_ fields - so once you + # highlight a particular field, no other fields in the index will have + # these partial indexes. + # + # Here's some examples: + # + # Field.new( + # Column.new(:name) + # ) + # + # Field.new( + # [Column.new(:first_name), Column.new(:last_name)], + # :as => :name, :sortable => true + # ) + # + # Field.new( + # [Column.new(:posts, :subject), Column.new(:posts, :content)], + # :as => :posts, :prefixes => true + # ) + # + def initialize(columns, options = {}) + @columns = Array(columns) + @associations = {} + + raise "Cannot define a field with no columns. Maybe you are trying to index a field with a reserved name (id, name). You can fix this error by using a symbol rather than a bare name (:id instead of id)." if @columns.empty? || @columns.any? { |column| !column.respond_to?(:__stack) } + + @alias = options[:as] + @sortable = options[:sortable] || false + @infixes = options[:infixes] || false + @prefixes = options[:prefixes] || false + @faceted = options[:facet] || false + end + + # Get the part of the SELECT clause related to this field. Don't forget + # to set your model and associations first though. + # + # This will concatenate strings if there's more than one data source or + # multiple data values (has_many or has_and_belongs_to_many associations). + # + def to_select_sql + clause = @columns.collect { |column| + column_with_prefix(column) + }.join(', ') + + clause = adapter.concatenate(clause) if concat_ws? + clause = adapter.group_concatenate(clause) if is_many? + + "#{adapter.cast_to_string clause } AS #{quote_column(unique_name)}" + end + + # Get the part of the GROUP BY clause related to this field - if one is + # needed. If not, all you'll get back is nil. The latter will happen if + # there's multiple data values (read: a has_many or has_and_belongs_to_many + # association). + # + def to_group_sql + case + when is_many?, ThinkingSphinx.use_group_by_shortcut? + nil + else + @columns.collect { |column| + column_with_prefix(column) + } + end + end + + # Returns the unique name of the field - which is either the alias of + # the field, or the name of the only column - if there is only one. If + # there isn't, there should be an alias. Else things probably won't work. + # Consider yourself warned. + # + def unique_name + if @columns.length == 1 + @alias || @columns.first.__name + else + @alias + end + end + + def to_facet + return nil unless @faceted + + ThinkingSphinx::Facet.new(self) + end + + private + + def adapter + @adapter ||= @model.sphinx_database_adapter + end + + def quote_column(column) + @model.connection.quote_column_name(column) + end + + # Indication of whether the columns should be concatenated with a space + # between each value. True if there's either multiple sources or multiple + # associations. + # + def concat_ws? + @columns.length > 1 || multiple_associations? + end + + # Checks whether any column requires multiple associations (which only + # happens for polymorphic situations). + # + def multiple_associations? + associations.any? { |col,assocs| assocs.length > 1 } + end + + # Builds a column reference tied to the appropriate associations. This + # dives into the associations hash and their corresponding joins to + # figure out how to correctly reference a column in SQL. + # + def column_with_prefix(column) + if column.is_string? + column.__name + elsif associations[column].empty? + "#{@model.quoted_table_name}.#{quote_column(column.__name)}" + else + associations[column].collect { |assoc| + assoc.has_column?(column.__name) ? + "#{@model.connection.quote_table_name(assoc.join.aliased_table_name)}" + + ".#{quote_column(column.__name)}" : + nil + }.compact.join(', ') + end + end + + # Could there be more than one value related to the parent record? If so, + # then this will return true. If not, false. It's that simple. + # + def is_many? + associations.values.flatten.any? { |assoc| assoc.is_many? } + end + end +end -- cgit v1.3