Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasc committed Feb 12, 2011
0 parents commit 03941c9
Show file tree
Hide file tree
Showing 12 changed files with 392 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.rdoc
@@ -0,0 +1 @@
WIP
9 changes: 9 additions & 0 deletions init.rb
@@ -0,0 +1,9 @@
require 'lib/sunspot'
require 'lib/composite_setup'
require 'lib/sunspot/dsl/fields'
require 'lib/sunspot/dsl/standard_query'
require 'lib/sunspot/type'
require 'lib/sunspot/field'
require 'lib/sunspot/setup'
require 'lib/sunspot/field_factory'
require 'lib/sunspot/indexer'
34 changes: 34 additions & 0 deletions lib/composite_setup.rb
@@ -0,0 +1,34 @@
module Sunspot
class CompositeSetup

# Collection of all attachment fields configured for any of the enclosed types.
#
# === Returns
#
# Array:: Text fields configured for the enclosed types
#
def all_attachment_fields
@attachment_fields ||= attachment_fields_hash.values.map { |set| set.to_a }.flatten
end

private

# Return a hash of field names to atachment field objects, containing all fields
# that are configured for any of the types enclosed.
#
# ==== Returns
#
# Hash:: Hash of field names to text field objects.
#
def attachment_fields_hash
@attachment_fields_hash ||=
setups.inject({}) do |hash, setup|
setup.all_attachment_fields.each do |text_field|
(hash[text_field.name] ||= Set.new) << text_field
end
hash
end
end

end
end
3 changes: 3 additions & 0 deletions lib/sunspot.rb
@@ -0,0 +1,3 @@
%w(rich_document).each do |filename|
require File.join(File.dirname(__FILE__), 'sunspot', filename)
end
16 changes: 16 additions & 0 deletions lib/sunspot/dsl/fields.rb
@@ -0,0 +1,16 @@
module Sunspot
module DSL
class Fields

# Added an attachment field, the attachment filename is passed to Solr for
# indexing by tiqa

def attachment(*names)
names.each do |name|
@setup.add_attachment_field_factory(name)
end
end

end
end
end
130 changes: 130 additions & 0 deletions lib/sunspot/dsl/standard_query.rb
@@ -0,0 +1,130 @@
module Sunspot
module DSL #:nodoc:
#
# This class presents a DSL for constructing queries using the
# Sunspot.search method. Methods of this class are available inside the
# search block. Much of the DSL's functionality is implemented by this
# class's superclasses, Sunspot::DSL::FieldQuery and Sunspot::DSL::Scope
#
# See Sunspot.search for usage examples
#
class StandardQuery < FieldQuery
include Paginatable, Adjustable

# Specify a phrase that should be searched as fulltext. Only +text+
# fields are searched - see DSL::Fields.text
#
# Keyword search is executed using Solr's dismax handler, which strikes
# a good balance between powerful and foolproof. In particular,
# well-matched quotation marks can be used to group phrases, and the
# + and - modifiers work as expected. All other special Solr boolean
# syntax is escaped, and mismatched quotes are ignored entirely.
#
# This method can optionally take a block, which is evaluated by the
# Fulltext DSL class, and exposes several powerful dismax features.
#
# ==== Parameters
#
# keywords<String>:: phrase to perform fulltext search on
#
# ==== Options
#
# :fields<Array>::
# List of fields that should be searched for keywords. Defaults to all
# fields configured for the types under search.
# :highlight<Boolean,Array>::
# If true, perform keyword highlighting on all searched fields. If an
# array of field names, perform highlighting on the specified fields.
# This can also be called from within the fulltext block.
# :minimum_match<Integer>::
# The minimum number of search terms that a result must match. By
# default, all search terms must match; if the number of search terms
# is less than this number, the default behavior applies.
# :tie<Float>::
# A tiebreaker coefficient for scores derived from subqueries that are
# lower-scoring than the maximum score subquery. Typically a near-zero
# value is useful. See
# http://wiki.apache.org/solr/DisMaxRequestHandler#tie_.28Tie_breaker.29
# for more information.
# :query_phrase_slop<Integer>::
# The number of words that can appear between the words in a
# user-entered phrase (i.e., keywords in quotes) and still match. For
# instance, in a search for "\"great pizza\"" with a phrase slop of 1,
# "great pizza" and "great big pizza" will match, but "great monster of
# a pizza" will not. Default behavior is a query phrase slop of zero.
#
def fulltext(keywords, options = {}, &block)
if keywords && !(keywords.to_s =~ /^\s*$/)
fulltext_query = @query.add_fulltext(keywords)
if field_names = options.delete(:fields)
Util.Array(field_names).each do |field_name|
@setup.text_fields(field_name).each do |field|
fulltext_query.add_fulltext_field(field, field.default_boost)
end
end
end
if minimum_match = options.delete(:minimum_match)
fulltext_query.minimum_match = minimum_match.to_i
end
if tie = options.delete(:tie)
fulltext_query.tie = tie.to_f
end
if query_phrase_slop = options.delete(:query_phrase_slop)
fulltext_query.query_phrase_slop = query_phrase_slop.to_i
end
if highlight_field_names = options.delete(:highlight)
if highlight_field_names == true
fulltext_query.add_highlight
else
highlight_fields = []
Util.Array(highlight_field_names).each do |field_name|
highlight_fields.concat(@setup.text_fields(field_name))
end
fulltext_query.add_highlight(highlight_fields)
end
end
if block && fulltext_query
fulltext_dsl = Fulltext.new(fulltext_query, @setup)
Util.instance_eval_or_call(
fulltext_dsl,
&block
)
end
if !field_names && (!fulltext_dsl || !fulltext_dsl.fields_added?)
@setup.all_text_fields.each do |field|
unless fulltext_query.has_fulltext_field?(field)
unless fulltext_dsl && fulltext_dsl.exclude_fields.include?(field.name)
fulltext_query.add_fulltext_field(field, field.default_boost)
end
end
end
end
if !field_names && (!fulltext_dsl || !fulltext_dsl.fields_added?)
unless @setup.all_attachment_fields.empty?
@setup.all_attachment_fields.each do |attachment_text_field|
unless fulltext_dsl && fulltext_dsl.exclude_fields.include?(attachment_text_field.name)
fulltext_query.add_fulltext_field(attachment_text_field, attachment_text_field.default_boost)
end
end
end
end
end
end
alias_method :keywords, :fulltext

def with(*args)
case args.first
when String, Symbol
field_name = args[0]
value = args.length > 1 ? args[1] : Scope::NONE
if value == Scope::NONE
return DSL::RestrictionWithNear.new(@setup.field(field_name.to_sym), @scope, @query, false)
end
end

# else
super
end
end
end
end
5 changes: 5 additions & 0 deletions lib/sunspot/field.rb
@@ -0,0 +1,5 @@
module Sunspot
class Field
attr_reader :default_boost
end
end
18 changes: 18 additions & 0 deletions lib/sunspot/field_factory.rb
@@ -0,0 +1,18 @@
module Sunspot
module FieldFactory

class Attachment
def initialize(name = nil, &block)
if block
@data_extractor = DataExtractor::BlockExtractor.new(&block)
else
@data_extractor = DataExtractor::AttributeExtractor.new(name)
end
end

def populate_document(document, model)
end
end

end
end
34 changes: 34 additions & 0 deletions lib/sunspot/indexer.rb
@@ -0,0 +1,34 @@
module Sunspot
class Indexer

def add_documents(documents)
documents_arr = Util.Array(documents)
docs_attach = []
docs_no_attach = []
documents_arr.each do |document|
if document.contains_attachment?
docs_attach << document
else
docs_no_attach << document
end
end

unless docs_no_attach.empty?
@connection.add(docs_no_attach)
else
Util.Array(docs_attach).each do |document|
document.add(@connection)
end
end
end


def document_for(model)
Sunspot::RichDocument.new(
:id => Adapters::InstanceAdapter.adapt(model).index_id,
:type => Util.superclasses_for(model.class).map { |clazz| clazz.name }
)
end

end
end
41 changes: 41 additions & 0 deletions lib/sunspot/rich_document.rb
@@ -0,0 +1,41 @@
module Sunspot
class RichDocument < RSolr::Message::Document
include Enumerable

def contains_attachment?
@fields.each do |field|
if field.name.to_s.include?("_attachment")
return true
end
end
return false
end



def add(connection)
params = {
:wt => :ruby
}

data = nil

@fields.each do |f|
if f.name.to_s.include?("_attachment") and f.value.present?
data = f.value.data
params['fmap.content'] = f.name
else
param_name = "literal.#{f.name.to_s}"
params[param_name] = [] unless params.has_key?(param_name)
params[param_name] << f.value
end
if f.attrs[:boost]
params["boost.#{f.name.to_s}"] = f.attrs[:boost]
end
end

solr_message = params
connection.send('update/extract', solr_message, data)
end
end
end
78 changes: 78 additions & 0 deletions lib/sunspot/setup.rb
@@ -0,0 +1,78 @@
module Sunspot
class Setup

def initialize(clazz)
@class_object_id = clazz.object_id
@class_name = clazz.name
@field_factories, @text_field_factories, @dynamic_field_factories, @attachment_field_factories,
@field_factories_cache, @text_field_factories_cache,
@dynamic_field_factories_cache, @attachment_field_factories_cache = *Array.new(8) { Hash.new }
@stored_field_factories_cache = Hash.new { |h, k| h[k] = [] }
@more_like_this_field_factories_cache = Hash.new { |h, k| h[k] = [] }
@dsl = DSL::Fields.new(self)
add_field_factory(:class, Type::ClassType.instance)
end



# Add field_factories for fulltext search on attachments
#
# ==== Parameters
#
def add_attachment_field_factory(name, options = {}, &block)
stored = options[:stored]
field_factory = FieldFactory::Static.new(name, Type::AttachmentType.instance, options, &block)
@attachment_field_factories[name] = field_factory
@attachment_field_factories_cache[field_factory.name] = field_factory
if stored
@attachment_field_factories_cache[field_factory.name] << field_factory
end
end



def text_fields(field_name)
text_field =
if field_factory = @text_field_factories_cache[field_name.to_sym]
field_factory.build
else
if field_factory = @attachment_field_factories_cache[field_name.to_sym]
field_factory.build
else
raise(
UnrecognizedFieldError,
"No text field configured for #{@class_name} with name '#{field_name}'"
)
end
end
[text_field]
end



def all_attachment_fields
attachment_field_factories.map { |field_factory| field_factory.build }
end



# Get the text field_factories associated with this setup as well as all inherited
# attachment field_factories
#
# ==== Returns
#
# Array:: Collection of all text field_factories associated with this setup
#
def attachment_field_factories
collection_from_inheritable_hash(:attachment_field_factories)
end


def all_field_factories
all_field_factories = []
all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories).concat(attachment_field_factories)
all_field_factories
end

end
end

0 comments on commit 03941c9

Please sign in to comment.