Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

removing unused whitespace

  • Loading branch information...
commit 4959550e843fcebbb84ed51ed8a281a6b238da22 1 parent 6935aac
@ryanb authored
Showing with 374 additions and 374 deletions.
  1. +2 −2 CHANGELOG.rdoc
  2. +4 −4 LICENSE
  3. +9 −9 README.rdoc
  4. +7 −7 lib/xapit.rb
  5. +6 −6 lib/xapit/adapters/abstract_adapter.rb
  6. +3 −3 lib/xapit/adapters/active_record_adapter.rb
  7. +1 −1  lib/xapit/adapters/data_mapper_adapter.rb
  8. +27 −27 lib/xapit/collection.rb
  9. +13 −13 lib/xapit/config.rb
  10. +1 −1  lib/xapit/document.rb
  11. +8 −8 lib/xapit/facet.rb
  12. +8 −8 lib/xapit/facet_blueprint.rb
  13. +6 −6 lib/xapit/facet_option.rb
  14. +21 −21 lib/xapit/index_blueprint.rb
  15. +17 −17 lib/xapit/indexers/abstract_indexer.rb
  16. +2 −2 lib/xapit/indexers/classic_indexer.rb
  17. +3 −3 lib/xapit/indexers/simple_indexer.rb
  18. +11 −11 lib/xapit/local_database.rb
  19. +23 −23 lib/xapit/membership.rb
  20. +12 −12 lib/xapit/query.rb
  21. +24 −24 lib/xapit/query_parsers/abstract_query_parser.rb
  22. +3 −3 lib/xapit/query_parsers/classic_query_parser.rb
  23. +6 −6 lib/xapit/query_parsers/simple_query_parser.rb
  24. +1 −1  rails_generators/xapit/USAGE
  25. +1 −1  rails_generators/xapit/xapit_generator.rb
  26. +3 −3 spec/xapit/adapters/active_record_adapter_spec.rb
  27. +33 −33 spec/xapit/collection_spec.rb
  28. +10 −10 spec/xapit/config_spec.rb
  29. +4 −4 spec/xapit/facet_blueprint_spec.rb
  30. +8 −8 spec/xapit/facet_option_spec.rb
  31. +11 −11 spec/xapit/facet_spec.rb
  32. +14 −14 spec/xapit/index_blueprint_spec.rb
  33. +13 −13 spec/xapit/indexers/abstract_indexer_spec.rb
  34. +3 −3 spec/xapit/indexers/classic_indexer_spec.rb
  35. +7 −7 spec/xapit/indexers/simple_indexer_spec.rb
  36. +8 −8 spec/xapit/membership_spec.rb
  37. +8 −8 spec/xapit/query_parsers/abstract_query_parser_spec.rb
  38. +3 −3 spec/xapit/query_parsers/classic_query_parser_spec.rb
  39. +14 −14 spec/xapit/query_parsers/simple_query_parser_spec.rb
  40. +7 −7 spec/xapit/query_spec.rb
  41. +9 −9 spec/xapit_member.rb
View
4 CHANGELOG.rdoc
@@ -5,14 +5,14 @@
* fixing nested search calls
* chain a separate search with "or_search" to find records matching either one
-
+
search("foo").or_search(:conditions => { :priority => 3 })
*0.2.6* (July 6th, 2009)
* search for field conditions in query string, only supported by ClassicQueryParser
-
+
search("age:17")
* check if xapian database exists before removing
View
8 LICENSE
@@ -1,5 +1,5 @@
-Copyright (c) 2008 Ryan Bates
-
+Copyright (c) 2011 Ryan Bates
+
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
@@ -7,10 +7,10 @@ without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
-
+
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
-
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
View
18 README.rdoc
@@ -17,7 +17,7 @@ To install as a Rails plugin, run this command.
Or to install as a gem in Rails first add this to config/environment.rb.
config.gem 'xapit'
-
+
And then install the gem and run the generator.
sudo rake gems:install
@@ -84,25 +84,25 @@ There are two projects in development to help improve this reindexing.
== Search
You can then perform a search on the model.
-
+
# perform a simple full text search
@articles = Article.search("phone")
-
+
# add pagination if you're using will_paginate
@articles = Article.search("phone", :per_page => 10, :page => params[:page])
-
+
# search based on indexed fields
@articles = Article.search("phone", :conditions => { :category_id => params[:category_id] })
-
+
# search for multiple negative conditions (doesn't match 3, 5, or 8)
@articles = Article.search(:not_conditions => { :category_id => [3, 5, 8] })
-
+
# search for range of conditions by number
@articles = Article.search(:conditions => { :released_at => 2.years.ago..Time.now })
-
+
# manually sort based on any number of indexed fields, sort defaults to most relevant
@articles = Article.search("phone", :order => [:category_id, :id], :descending => true)
-
+
# basic boolean matching is supported
@articles = Article.search("phone OR fax NOT email")
@@ -193,5 +193,5 @@ This project can be found on github at the following URL.
http://github.com/ryanb/xapit
-If you would like to contribute to this project, please fork the
+If you would like to contribute to this project, please fork the
repository and send me a pull request.
View
14 lib/xapit.rb
@@ -9,20 +9,20 @@ module Xapit
def self.index_all(*args, &block)
IndexBlueprint.index_all(*args, &block)
end
-
+
# Used to perform a search on all indexed models. The returned collection can
# contain instances of different classes which were indexed.
- #
+ #
# # perform a simple full text search
# @records = Xapit.search("phone")
- #
+ #
# See Xapit::Membership for details on search options.
def self.search(*args)
Collection.new(nil, *args)
end
-
+
# Setup configuration options. The following options are supported.
- #
+ #
# <tt>:database_path</tt>: Where the database is stored.
# <tt>:stemming</tt>: The language to use for stemming, defaults to "english".
# <tt>:spelling</tt>: True or false to enable/disable spelling, defaults to true.
@@ -33,12 +33,12 @@ def self.search(*args)
def self.setup(*args)
Config.setup(*args)
end
-
+
# Removes the configured database file and clears the stored one in memory.
def self.remove_database
Config.remove_database
end
-
+
def self.serialize_value(value)
if value.kind_of?(Date)
Xapian.sortable_serialise(value.to_time.to_i)
View
12 lib/xapit/adapters/abstract_adapter.rb
@@ -8,35 +8,35 @@ def self.inherited(subclass)
@subclasses ||= []
@subclasses << subclass
end
-
+
# Returns all adapter classes.
def self.subclasses
@subclasses
end
-
+
# Sets the @target instance, this is the class the adapter needs to forward
# its messages to.
def initialize(target)
@target = target
end
-
+
# Used to determine if the given adapter should be used for the passed in class.
# Usually one will see if it inherits from another class (ActiveRecord::Base)
def self.for_class?(member_class)
raise "To be implemented in subclass"
end
-
+
# Fetch a single record by the given id.
# The args are the same as those passed from the XapitMember#xapit call.
def find_single(id, *args)
raise "To be implemented in subclass"
end
-
+
# Fetch multiple records from the passed in array of ids.
def find_multiple(ids)
raise "To be implemented in subclass"
end
-
+
# Iiterate through all records using the given parameters.
# It should yield to the block and pass in each record individually.
# The args are the same as those passed from the XapitMember#xapit call.
View
6 lib/xapit/adapters/active_record_adapter.rb
@@ -4,15 +4,15 @@ class ActiveRecordAdapter < AbstractAdapter
def self.for_class?(member_class)
member_class.ancestors.map(&:to_s).include? "ActiveRecord::Base"
end
-
+
def find_single(id, *args)
@target.find_by_id(id, *args)
end
-
+
def find_multiple(ids)
@target.find(ids)
end
-
+
def find_each(*args, &block)
@target.find_each(*args, &block)
end
View
2  lib/xapit/adapters/data_mapper_adapter.rb
@@ -4,7 +4,7 @@ class DataMapperAdapter < AbstractAdapter
def self.for_class?(member_class)
member_class.ancestors.map(&:to_s).include? "DataMapper::Resource"
end
-
+
# TODO override the rest of the methods here...
end
end
View
54 lib/xapit/collection.rb
@@ -1,7 +1,7 @@
module Xapit
# This is the object which is returned when performing a search. It behaves like an array, so you do not need
# to worry about fetching the results separately. Just loop through this collection.
- #
+ #
# The results are lazy loading, meaning it does not perform the query on the database until it has to.
# This allows you to string queries onto one another.
#
@@ -14,7 +14,7 @@ class Collection
delegate m, :to => :results unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
end
delegate :query, :base_query, :base_query=, :extra_queries, :extra_queries=, :to => :@query_parser
-
+
def self.search_similar(member, *args)
collection = new(member.class, *args)
indexer = SimpleIndexer.new(member.class.xapit_index_blueprint)
@@ -23,56 +23,56 @@ def self.search_similar(member, *args)
collection.base_query = query
collection
end
-
+
def initialize(*args)
@query_parser = Config.query_parser.new(*args)
end
-
- # Returns an array of results. You should not need to call this directly because most methods are
+
+ # Returns an array of results. You should not need to call this directly because most methods are
# automatically delegated to this array.
def results
@results ||= fetch_results
end
-
+
# The number of total records found despite any pagination settings.
def size
@query_parser.query.count
end
alias_method :total_entries, :size # alias to total_entries to support will_paginate
-
+
# Returns true if no results are found.
def empty?
@results ? @results.empty? : size.zero?
end
-
+
# The first record in the result set.
def first
fetch_results(:offset => 0, :limit => 1).first
end
-
+
# The last record in the result set.
def last
fetch_results(:offset => size-1, :limit => 1).last
end
-
+
# Perform another search on this one, inheriting all options already passed.
# See Xapit::Membership for search options.
#
# Article.search("kite").search("sky") # only performs one query
- #
+ #
def search(*args)
options = args.extract_options!
collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
collection.base_query = @query_parser.query
collection
end
-
+
# Chain another search returning all records matched by either this search or the previous search
# Inherits all options passed in earlier search (such as :page and :order)
# See Xapit::Membership for search options.
#
# Article.search("kite").or_search(:conditions => { :priority => 1 })
- #
+ #
def or_search(*args)
options = args.extract_options!
collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
@@ -81,44 +81,44 @@ def or_search(*args)
collection.extra_queries << @query_parser.primary_query
collection
end
-
+
# The page number we are currently on.
def current_page
@query_parser.current_page
end
-
+
# How many records to display on each page, defaults to 20. Sets with :per_page option when performing search.
def per_page
@query_parser.per_page
end
-
+
# The offset for the current page
def offset
@query_parser.offset
end
-
+
# Total number of pages with found results.
def total_pages
(size / per_page.to_f).ceil
end
-
+
# The previous page number. Returns nil if on first page.
def previous_page
current_page > 1 ? (current_page - 1) : nil
end
-
+
# The next page number. Returns nil if on last page.
def next_page
current_page < total_pages ? (current_page + 1): nil
end
-
+
# Xapit::Facet objects matching this search query. See class for details.
def facets
all_facets.select do |facet|
facet.options.size > 0
end
end
-
+
# Xapit::FacetOption objects which are currently applied to search (through :facets option). Use this to
# display the facets which are currently applied.
#
@@ -142,17 +142,17 @@ def applied_facet_options
option
end
end
-
+
# Includes a suggested variation of a term which will get many more results. Returns nil if no suggestion.
- #
+ #
# <% if @articles.spelling_suggestion %>
# Did you mean <%= link_to h(@articles.spelling_suggestion), :overwrite_params => { :keywords => @articles.spelling_suggestion } %>?
# <% end %>
- #
+ #
def spelling_suggestion
@query_parser.spelling_suggestion
end
-
+
# All Xapit::Facet objects, even if they do not include options.
# Usually you'll want to call Collection#facets
def all_facets
@@ -160,9 +160,9 @@ def all_facets
Facet.new(facet_blueprint, @query_parser.query, @query_parser.facet_identifiers)
end
end
-
+
private
-
+
# TODO this could use some refactoring
# See issue #11 for why this is so complex.
def fetch_results(options = {})
View
26 lib/xapit/config.rb
@@ -3,13 +3,13 @@ module Xapit
class Config
class << self
attr_reader :options
-
+
# See Xapit#setup
def setup(options = {})
@database = nil
@options = options.reverse_merge(default_options)
end
-
+
def default_options
{
:indexer => SimpleIndexer,
@@ -18,54 +18,54 @@ def default_options
:stemming => "english"
}
end
-
+
# See if setup options are already set.
def setup?
@options
end
-
+
# The configured path to the database.
def path
@options[:database_path]
end
-
+
# Configure another database to use as a template.
# It will copy this database to the database_path before attempting to open it.
# This is very useful for testing since creating a database is slow.
def template_path
@options[:template_path]
end
-
+
def query_parser
@options[:query_parser]
end
-
+
def indexer
@options[:indexer]
end
-
+
def spelling?
@options[:spelling]
end
-
+
def stemming
@options[:stemming]
end
-
+
def breadcrumb_facets?
@options[:breadcrumb_facets]
end
-
+
def database
@database ||= Xapit::LocalDatabase.new(path, template_path)
end
-
+
# Removes the configured database file and clears the stored one in memory.
def remove_database
FileUtils.rm_rf(path) if File.exist? File.join(path, "record.DB")
@database = nil
end
-
+
# Clear the current database from memory. Unfortunately this is a hack because
# Xapian doesn't provide a "close" method on the database. We just have to hope
# no other references are lying around.
View
2  lib/xapit/document.rb
@@ -1,7 +1,7 @@
module Xapit
class Document
attr_accessor :id, :data, :terms, :term_weights, :values, :value_indexes, :spellings
-
+
def initialize
@terms = []
@term_weights = []
View
16 lib/xapit/facet.rb
@@ -1,7 +1,7 @@
module Xapit
# Facets allow users to further filter the result set based on certain attributes.
# You should fetch facets by calling "facets" on a Xapit::Collection search result.
- #
+ #
# <% for facet in @articles.facets %>
# <%= facet.name %>
# <% for option in facet.options %>
@@ -13,19 +13,19 @@ module Xapit
# See Xapit::FacetBlueprint for details on how to index a facet.
class Facet
attr_accessor :existing_facet_identifiers
-
+
def initialize(blueprint, query, existing_facet_identifiers)
@blueprint = blueprint
@query = query
@existing_facet_identifiers = existing_facet_identifiers
end
-
+
# Xapit::FacetOption objects for this facet which match the current query.
# Hides options which don't narrow down results.
def options
unfiltered_options.select { |o| o.count < @query.count }
end
-
+
# Xapit::FacetOption objects for this facet which match the current query.
# Includes options which may not narrow down result set.
def unfiltered_options
@@ -38,7 +38,7 @@ def unfiltered_options
end
end.compact.sort_by(&:name)
end
-
+
def matching_identifiers
result = {}
matches.each do |match|
@@ -52,14 +52,14 @@ def matching_identifiers
end
result
end
-
+
# The name of the facet. See Xapit::FacetBlueprint for details.
def name
@blueprint.name
end
-
+
private
-
+
def matches
@query.matches(:offset => 0, :limit => 1000, :collapse_key => @blueprint.position)
end
View
16 lib/xapit/facet_blueprint.rb
@@ -8,44 +8,44 @@ module Xapit
#
# Multiple facet values are supported for a single record. All you need to do is return an array of
# values instead of a single string.
- #
+ #
# def category_names
# categories.map(&:name) # => ["Toys", "Clothing"]
# end
- #
+ #
class FacetBlueprint
attr_reader :member_class
attr_reader :position
attr_reader :attribute
-
+
def initialize(member_class, position, attribute, custom_name = nil)
@member_class = member_class
@position = position
@attribute = attribute
@custom_name = custom_name
end
-
+
def identifiers_for(member)
values_for(member).map do |value|
Digest::SHA1.hexdigest(@attribute.to_s + value)[0..6]
end
end
-
+
# The name of the facet. This will return the custom name if given while setting up the index,
# or default to humanizing the attribute name.
def name
@custom_name || @attribute.to_s.humanize
end
-
+
def save_facet_options_for(member)
values_for(member).map do |value|
option = FacetOption.new(member.class.name, @attribute.to_s, value)
option.save
end
end
-
+
private
-
+
def values_for(member)
value = member.send(@attribute)
if value.kind_of? Array
View
12 lib/xapit/facet_option.rb
@@ -2,7 +2,7 @@ module Xapit
# A facet option is a specific value or choice for a facet. See Xapit::Facet for details on how to use it.
class FacetOption
attr_accessor :facet, :name, :existing_facet_identifiers, :count
-
+
# Fetch a facet option given an id.
def self.find(id)
match = Query.new("Q#{name}-#{id}").matches(:offset => 0, :limit => 1).first
@@ -13,21 +13,21 @@ def self.find(id)
new(class_name.to_s, facet_attribute.to_s, name.to_s)
end
end
-
+
# See if the given facet option exists with this id.
def self.exist?(id)
Query.new("Q#{name}-#{id}").count >= 1
end
-
+
def initialize(class_name, facet_attribute, name)
@facet = class_name.constantize.xapit_facet_blueprint(facet_attribute) if class_name && facet_attribute
@name = name
end
-
+
def identifier
Digest::SHA1.hexdigest(facet.attribute.to_s + name)[0..6]
end
-
+
# Saves the given facet option to the database if it hasn't been already.
def save
unless self.class.exist?(identifier)
@@ -37,7 +37,7 @@ def save
Xapit::Config.database.add_document(doc)
end
end
-
+
# Converts the facet to be used in a URL. It adds to the existing ones for convenience.
# If this facet option is currently selected, then this will return all selected facets except
# this one. This conveniently allows you to use this as both an "add this facet" and "remove this facet" link.
View
42 lib/xapit/index_blueprint.rb
@@ -6,7 +6,7 @@ class IndexBlueprint
attr_reader :field_attributes
attr_reader :sortable_attributes
attr_reader :facets
-
+
# Indexes all classes known to have an index blueprint defined.
def self.index_all
load_models
@@ -15,7 +15,7 @@ def self.index_all
blueprint.index_all
end
end
-
+
def initialize(member_class, *args)
@member_class = member_class
@args = args
@@ -27,7 +27,7 @@ def initialize(member_class, *args)
@@instances[member_class] = self # TODO make this thread safe
@indexer = SimpleIndexer.new(self)
end
-
+
# Adds a text attribute. Each word in the text will be indexed as a separate term allowing full text searching.
# Text terms are what is searched by the primary string in a search query.
#
@@ -37,7 +37,7 @@ def initialize(member_class, *args)
# that attribute to have a higher rank. The default weight is 1. Decimal (0.5) weight values are not supported.
#
# index.text :name, :weight => 10
- #
+ #
def text(*attributes, &proc)
options = attributes.extract_options!
options[:proc] ||= proc
@@ -45,32 +45,32 @@ def text(*attributes, &proc)
@text_attributes[attribute] = options
end
end
-
+
# Adds a field attribute. Field terms are not split by word so it is not designed for full text search.
# Instead you can filter through a field using the :conditions hash in a search query.
#
# Article.search(:conditions => { :priority => 5 })
- #
+ #
# Multiple field values are supported if the given attribute is an array.
- #
+ #
# def priority
# [3, 5] # will match priority search for 3 or 5
# end
- #
+ #
def field(*attributes)
@field_attributes += attributes
end
-
+
# Adds a facet attribute. See Xapit::FacetBlueprint and Xapit::Facet for details.
def facet(*args, &block)
@facets << FacetBlueprint.new(@member_class, @facets.size, *args, &block)
end
-
+
# Adds a sortable attribute for use with the :order option in a search call.
def sortable(*attributes)
@sortable_attributes += attributes
end
-
+
# Indexes all records of this blueprint class. It does this using the ".find_each" method on the member class.
# You will likely want to call Xapit.remove_database before this.
def index_all
@@ -78,27 +78,27 @@ def index_all
@indexer.add_member(member)
end
end
-
+
# The Xapian value index position of a sortable attribute
def position_of_sortable(sortable_attribute)
index = sortable_attributes.map(&:to_s).index(sortable_attribute.to_s)
raise "Unable to find indexed sortable attribute \"#{sortable_attribute}\" in #{@member_class} sortable attributes: #{sortable_attributes.inspect}" if index.nil?
index + facets.size
end
-
+
# The Xapian value index position of a field attribute
def position_of_field(field_attribute)
index = field_attributes.map(&:to_s).index(field_attribute.to_s)
raise "Unable to find indexed field attribute \"#{field_attribute}\" in #{@member_class} field attributes: #{field_attributes.inspect}" if index.nil?
index + facets.size + sortable_attributes.size
end
-
+
# Add a single record to the index if it matches the xapit options.
def create_record(member_id)
member = @member_class.xapit_adapter.find_single(member_id, *@args)
@indexer.add_member(member) if member
end
-
+
# Update a single record in the index. If the record does not match the xapit
# conditions then it is removed from the index instead.
def update_record(member_id)
@@ -109,30 +109,30 @@ def update_record(member_id)
destroy_record(member_id)
end
end
-
+
# Remove a single record from the index.
def destroy_record(member_id)
Xapit::Config.database.delete_document("Q#{@member_class}-#{member_id}")
end
-
+
private
-
+
# Make sure all models are loaded - without reloading any that
# ActiveRecord::Base is already aware of (otherwise we start to hit some
# messy dependencies issues).
- #
+ #
# Taken from thinking-sphinx
def self.load_models
if defined? Rails
base = "#{Rails.root}/app/models/"
Dir["#{base}**/*.rb"].each do |file|
model_name = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\1')
-
+
next if model_name.nil?
next if ::ActiveRecord::Base.send(:subclasses).detect { |model|
model.name == model_name
}
-
+
begin
model_name.camelize.constantize
rescue LoadError
View
34 lib/xapit/indexers/abstract_indexer.rb
@@ -3,15 +3,15 @@ class AbstractIndexer
def initialize(blueprint)
@blueprint = blueprint
end
-
+
def add_member(member)
database.add_document(document_for(member))
end
-
+
def update_member(member)
database.replace_document("Q#{member.class}-#{member.id}", document_for(member))
end
-
+
def document_for(member)
document = Xapit::Document.new
document.data = "#{member.class}-#{member.id}"
@@ -24,26 +24,26 @@ def document_for(member)
save_facet_options_for(member)
document
end
-
+
def index_terms(terms, document)
terms.each do |term|
document.terms << term
document.spellings << term if Config.spelling?
end
end
-
+
def index_text_attributes(member, document)
# to be overridden by subclass
end
-
+
def other_terms(member)
base_terms(member) + field_terms(member) + facet_terms(member)
end
-
+
def base_terms(member)
["C#{member.class}", "Q#{member.class}-#{member.id}"]
end
-
+
def field_terms(member)
@blueprint.field_attributes.map do |name|
[member.send(name)].flatten.map do |value|
@@ -56,13 +56,13 @@ def field_terms(member)
end
end.flatten
end
-
+
def facet_terms(member)
@blueprint.facets.map do |facet|
facet.identifiers_for(member).map { |id| "F#{id}" }
end.flatten
end
-
+
# used primarily by search similar functionality
def text_terms(member) # REFACTORME some duplicaiton with simple indexer
@blueprint.text_attributes.map do |name, options|
@@ -74,11 +74,11 @@ def text_terms(member) # REFACTORME some duplicaiton with simple indexer
end
end.flatten
end
-
+
def values(member)
facet_values(member) + sortable_values(member) + field_values(member)
end
-
+
def sortable_values(member)
@blueprint.sortable_attributes.map do |sortable|
value = member.send(sortable)
@@ -86,7 +86,7 @@ def sortable_values(member)
Xapit.serialize_value(value)
end
end
-
+
# TODO remove duplication with sortable_values
def field_values(member)
@blueprint.field_attributes.map do |sortable|
@@ -95,21 +95,21 @@ def field_values(member)
Xapit.serialize_value(value)
end
end
-
+
def facet_values(member)
@blueprint.facets.map do |facet|
facet.identifiers_for(member).join("-")
end
end
-
+
def save_facet_options_for(member)
@blueprint.facets.each do |facet|
facet.save_facet_options_for(member)
end
end
-
+
private
-
+
def database
Config.database
end
View
4 lib/xapit/indexers/classic_indexer.rb
@@ -13,11 +13,11 @@ def index_text_attributes(member, document)
end
end
end
-
+
def term_generator
@term_generator ||= create_term_generator
end
-
+
def create_term_generator
term_generator = Xapian::TermGenerator.new
term_generator.set_flags(Xapian::TermGenerator::FLAG_SPELLING, 0) if Config.spelling?
View
6 lib/xapit/indexers/simple_indexer.rb
@@ -15,13 +15,13 @@ def index_text_attributes(member, document)
end
end
end
-
+
def stemmed_terms_for_attribute(member, name, options)
terms_for_attribute(member, name, options).map do |term|
"Z#{stemmer.call(term)}"
end
end
-
+
def terms_for_attribute(member, name, options)
content = member.send(name)
if options[:proc]
@@ -32,7 +32,7 @@ def terms_for_attribute(member, name, options)
content.to_s.split(/\s+/u).map { |w| w.gsub(/[^\w]/u, "") }.map(&:downcase)
end
end
-
+
def stemmer
@stemmer ||= Xapian::Stem.new(Config.stemming)
end
View
22 lib/xapit/local_database.rb
@@ -4,41 +4,41 @@ def initialize(path, template_path)
@path = path
@template_path = template_path
end
-
+
def readable_database
writable_database
end
-
+
def writable_database
@writable_database ||= generate_database
end
-
+
def add_document(document)
writable_database.add_document(build_xapian_document(document))
end
-
+
def delete_document(id)
writable_database.delete_document(id)
end
-
+
def replace_document(id, document)
writable_database.replace_document(id, build_xapian_document(document))
end
-
+
def get_spelling_suggestion(term)
readable_database.get_spelling_suggestion(term)
end
-
+
def add_spelling(term)
writable_database.add_spelling(term)
end
-
+
def doccount
readable_database.doccount
end
-
+
private
-
+
def generate_database
FileUtils.mkdir_p(File.dirname(@path)) unless File.exist?(File.dirname(@path))
if @template_path && !File.exist?(@path)
@@ -46,7 +46,7 @@ def generate_database
end
Xapian::WritableDatabase.new(@path, Xapian::DB_CREATE_OR_OPEN)
end
-
+
def build_xapian_document(document)
xapian_doc = Xapian::Document.new
xapian_doc.data = "#{document.id}#{document.data}"
View
46 lib/xapit/membership.rb
@@ -5,10 +5,10 @@ module Membership
def self.included(base)
base.extend ClassMethods
end
-
+
module ClassMethods
# Simply call "xapit" on a class and pass a block to define the indexed attributes.
- #
+ #
# class Article < ActiveRecord::Base
# xapit do |index|
# index.text :name, :content
@@ -17,32 +17,32 @@ module ClassMethods
# index.sortable :id, :category_id
# end
# end
- #
+ #
# First we index "name" and "content" attributes for full text searching. The "category_id" field is indexed for :conditions searching. The "author_name" is indexed as a facet with "Author" being the display name of the facet. See the facets section below for details. Finally the "id" and "category_id" attributes are indexed as sortable attributes so they can be included in the :order option in a search.
- #
+ #
# Because the indexing happens in Ruby these attributes do no have to be database columns. They can be simple Ruby methods. For example, the "author_name" attribute mentioned above can be defined like this.
- #
+ #
# def author_name
# author.name
# end
- #
+ #
# This way you can create a completely custom facet by simply defining your own method
- #
+ #
# You can also pass any find options to the xapit method to determine what gets indexed and improve performance with eager loading or a different batch size.
- #
+ #
# xapit(:batch_size => 100, :include => :author, :conditions => { :visible => true })
#
# If you pass in a block you can customize how the text words will be devided (instead of by simply white space).
- #
+ #
# xapit do |index|
# index.text(:keywords) { |words| words.split(', ') }
# end
- #
+ #
# You can specify a :weight option to give a text attribute more importance. This will cause search terms matching
# that attribute to have a higher rank. The default weight is 1. Decimal (0.5) weight values are not supported.
#
# index.text :name, :weight => 10
- #
+ #
def xapit(*args)
@xapit_index_blueprint = IndexBlueprint.new(self, *args)
yield(@xapit_index_blueprint)
@@ -50,36 +50,36 @@ def xapit(*args)
include XapitSync::Membership if defined? XapitSync
end
end
-
+
module AdditionalMethods
def self.included(base)
base.extend ClassMethods
base.send(:attr_accessor, :xapit_relevance) # is there a better way to do this?
end
-
+
# Find similar records to the given model. It takes the same arguments as Membership::AdditionalMethods::ClassMethods#search to further narrow down the results.
def search_similar(*args)
Collection.search_similar(self, *args)
end
-
+
module ClassMethods
# Used to perform a search on a model.
- #
+ #
# # perform a simple full text search
# @articles = Article.search("phone")
- #
+ #
# # add pagination if you're using will_paginate
# @articles = Article.search("phone", :per_page => 10, :page => params[:page])
- #
+ #
# # search based on indexed fields
# @articles = Article.search("phone", :conditions => { :category_id => params[:category_id] })
- #
+ #
# # search for multiple negative conditions (doesn't match 3, 5, or 8)
# @articles = Article.search(:not_conditions => { :category_id => [3, 5, 8] })
- #
+ #
# # search for range of conditions by number
# @articles = Article.search(:conditions => { :released_at => 2.years.ago..Time.now })
- #
+ #
# # manually sort based on any number of indexed fields, sort defaults to most relevant
# @articles = Article.search("phone", :order => [:category_id, :id], :descending => true)
#
@@ -101,12 +101,12 @@ module ClassMethods
def search(*args)
Collection.new(self, *args)
end
-
+
# The Xapit::IndexBlueprint object used for this class.
def xapit_index_blueprint
@xapit_index_blueprint
end
-
+
# The Xapit::AbstractAdapter used to perform database queries on.
def xapit_adapter
@xapit_adapter ||= begin
@@ -118,7 +118,7 @@ def xapit_adapter
end
end
end
-
+
# Finds a Xapit::FacetBlueprint for the given attribute.
def xapit_facet_blueprint(attribute)
result = xapit_index_blueprint.facets.detect { |f| f.attribute.to_s == attribute.to_s }
View
24 lib/xapit/query.rb
@@ -4,23 +4,23 @@ module Xapit
# You may be looking for Xapit::Collection instead.
class Query
attr_reader :xapian_query
-
+
def initialize(*args)
@xapian_query = build_xapian_query(*args)
end
-
+
def and_query(*args)
merge_query(:and, *args)
end
-
+
def or_query(*args)
merge_query(:or, *args)
end
-
+
def not_query(*args)
merge_query(:not, *args)
end
-
+
def matchset(options = {})
options.reverse_merge! :offset => 0, :sort_descending => false
enquire = Xapian::Enquire.new(Config.database.readable_database)
@@ -35,18 +35,18 @@ def matchset(options = {})
enquire.query = @xapian_query
enquire.mset(options[:offset], options[:limit])
end
-
+
def matches(options = {})
matchset(options).matches
end
-
+
def count
# a bit of a hack to get more accurate count estimate
@count ||= matchset(:limit => Config.database.readable_database.doccount).matches_estimated
end
-
+
private
-
+
def merge_query(operator, *args)
if args.first.blank?
self
@@ -54,7 +54,7 @@ def merge_query(operator, *args)
Xapit::Query.new([@xapian_query, build_xapian_query(*args)], operator)
end
end
-
+
def build_xapian_query(query, operator = :and)
extract_queries(query, operator).inject(nil) do |query, extra_query|
if query
@@ -66,7 +66,7 @@ def build_xapian_query(query, operator = :and)
end
end
end
-
+
def extract_queries(query, operator)
queries = [query].flatten
terms = queries.select { |q| q.kind_of? String }
@@ -76,7 +76,7 @@ def extract_queries(query, operator)
(queries - terms) + [Xapian::Query.new(xapian_operator(operator), terms)]
end
end
-
+
def xapian_operator(operator)
case operator
when :and then Xapian::Query::OP_AND
View
48 lib/xapit/query_parsers/abstract_query_parser.rb
@@ -3,14 +3,14 @@ class AbstractQueryParser
attr_reader :member_class, :options
attr_writer :base_query
attr_accessor :extra_queries
-
+
def initialize(*args)
@options = args.extract_options!
@member_class = args[0]
@search_text = args[1].to_s
@extra_queries = []
end
-
+
def query
if @extra_queries.blank?
primary_query
@@ -18,7 +18,7 @@ def query
Query.new([primary_query] + @extra_queries, :or)
end
end
-
+
def primary_query
if (@search_text.split + condition_terms + not_condition_terms + facet_terms).empty?
base_query
@@ -26,19 +26,19 @@ def primary_query
@query ||= base_query.and_query(xapian_query_from_text(@search_text)).and_query(condition_terms + facet_terms).not_query(not_condition_terms)
end
end
-
+
def current_page
@options[:page] ? @options[:page].to_i : 1
end
-
+
def per_page
@options[:per_page] ? @options[:per_page].to_i : 20
end
-
+
def offset
per_page*(current_page-1)
end
-
+
def sort_by_values
if @options[:order] && @member_class
index = @member_class.xapit_index_blueprint
@@ -51,15 +51,15 @@ def sort_by_values
end
end
end
-
+
def base_query
@base_query ||= initial_query
end
-
+
def initial_query
Query.new(initial_query_strings, :or)
end
-
+
def initial_query_strings
if classes.empty?
[""]
@@ -67,19 +67,19 @@ def initial_query_strings
classes.map { |klass| "C#{klass.name}" }
end
end
-
+
def classes
(@options[:classes] || [@member_class]).compact
end
-
+
def condition_terms
parse_conditions(@options[:conditions])
end
-
+
def not_condition_terms
parse_conditions(@options[:not_conditions])
end
-
+
def facet_terms
if @options[:facets]
facet_identifiers.map do |identifier|
@@ -89,11 +89,11 @@ def facet_terms
[]
end
end
-
+
def facet_identifiers
@options[:facets].kind_of?(String) ? @options[:facets].split('-') : (@options[:facets] || [])
end
-
+
def spelling_suggestion
raise "Spelling has been disabled. Enable spelling in Xapit.setup." unless Config.spelling?
if [@search_text, *@search_text.scan(/\w+/)].all? { |term| term_suggestion(term).nil? }
@@ -105,16 +105,16 @@ def spelling_suggestion
end
end
end
-
+
def term_suggestion(term)
suggestion = Config.database.get_spelling_suggestion(term.downcase)
suggestion.blank? ? nil : suggestion
end
-
+
def matchset(options = {})
query.matchset(query_options.merge(options))
end
-
+
def query_options
{
:offset => offset,
@@ -123,9 +123,9 @@ def query_options
:sort_descending => @options[:descending]
}
end
-
+
private
-
+
def parse_conditions(conditions)
if conditions.kind_of? Array
[Query.new(conditions.map { |hash| Query.new(condition_terms_from_hash(hash)) }, :or)]
@@ -135,7 +135,7 @@ def parse_conditions(conditions)
[]
end
end
-
+
def condition_terms_from_hash(conditions)
conditions.map do |name, value|
if value.kind_of? Array
@@ -145,7 +145,7 @@ def condition_terms_from_hash(conditions)
end
end.flatten
end
-
+
def condition_term(name, value)
if value.kind_of?(Range) && @member_class
position = @member_class.xapit_index_blueprint.position_of_field(name)
@@ -161,7 +161,7 @@ def condition_term(name, value)
"X#{name}-#{value.to_s.downcase}"
end
end
-
+
# Expands the wildcard in the term (just at the end) and returns a query
# which will match any term that starts with the given term.
def wildcard_query(term, prefix = "")
View
6 lib/xapit/query_parsers/classic_query_parser.rb
@@ -3,15 +3,15 @@ class ClassicQueryParser < AbstractQueryParser
def xapian_query_from_text(text)
xapian_parser.parse_query(cleanup_text(text), Xapian::QueryParser::FLAG_WILDCARD | Xapian::QueryParser::FLAG_PHRASE | Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_LOVEHATE)
end
-
+
def xapian_parser
@xapian_parser ||= build_xapian_parser
end
-
+
def cleanup_text(text)
text.gsub(/\b([a-z])\*/i, "\\1").gsub(/[^\w\*\s:]/u, "")
end
-
+
def build_xapian_parser
parser = Xapian::QueryParser.new
parser.database = Config.database.readable_database
View
12 lib/xapit/query_parsers/simple_query_parser.rb
@@ -21,18 +21,18 @@ def xapian_query(instructions = nil)
end
query
end
-
+
def parsed
parse(@search_text.downcase)
end
-
+
def xapian_query_from_text(text)
xapian_query(parse(text.downcase))
end
-
+
private
-
-
+
+
def parse(text)
if text.kind_of? Array
[:and, *text]
@@ -67,7 +67,7 @@ def parse(text)
end
end
end
-
+
def stemmer
@stemmer ||= Xapian::Stem.new(Config.stemming)
end
View
2  rails_generators/xapit/USAGE
@@ -2,7 +2,7 @@ Description:
Generates files necessary to setup Xapit. This includes an initializer
to specify the configuration (setup_xapit.rb) and a rake file to load
the rake tasks (xapit.rake).
-
+
IMPORTANT: Only use this generator if you are using the gem version of
Xapit, it is not needed if you are installing via Rails plugin.
View
2  rails_generators/xapit/xapit_generator.rb
@@ -3,7 +3,7 @@ def manifest
record do |m|
m.directory "config/initializers"
m.file "setup_xapit.rb", "config/initializers/setup_xapit.rb"
-
+
m.directory "lib/tasks"
m.file "xapit.rake", "lib/tasks/xapit.rake"
end
View
6 spec/xapit/adapters/active_record_adapter_spec.rb
@@ -8,21 +8,21 @@
stub(klass).ancestors { ["ActiveRecord::Base"] }
Xapit::ActiveRecordAdapter.should be_for_class(klass)
end
-
+
it "should pass find_single to find method to target" do
target = Object.new
mock(target).find_by_id(1, :conditions => "foo") { :record }
adapter = Xapit::ActiveRecordAdapter.new(target)
adapter.find_single(1, :conditions => "foo").should == :record
end
-
+
it "should pass find_multiple to find method to target" do
target = Object.new
mock(target).find([1, 2]) { :record }
adapter = Xapit::ActiveRecordAdapter.new(target)
adapter.find_multiple([1, 2]).should == :record
end
-
+
it "should pass find_each to target" do
target = Object.new
mock(target).find_each(:args) { 5 }
View
66 spec/xapit/collection_spec.rb
@@ -10,55 +10,55 @@
index.sortable :name
end
end
-
+
describe "indexed" do
before(:each) do
@hello = XapitMember.new(:name => "hello world")
@foo = XapitMember.new(:name => "foo bar")
Xapit.index_all
end
-
+
it "should find all xapit members in database given nil" do
Xapit::Collection.new(XapitMember, nil).should == [@hello, @foo]
end
-
+
it "should matching xapit member given a word" do
Xapit::Collection.new(XapitMember, "foo").should == [@foo]
end
-
+
it "should not be case sensitive on query matching" do
Xapit::Collection.new(XapitMember, "BAR Foo").should == [@foo]
end
-
+
it "should have 2 records for empty string" do
Xapit::Collection.new(XapitMember, "").size.should == 2
end
-
+
it "should not be empty for blank query" do
Xapit::Collection.new(XapitMember, "").empty?.should be_false
end
-
+
it "should filter by conditions, case insensitive" do
Xapit::Collection.new(XapitMember, "", :conditions => { :name => "HELLO world"}).should == [@hello]
end
-
+
it "should know first entry" do
Xapit::Collection.new(XapitMember, "").first.should == @hello
end
-
+
it "should know last entry" do
Xapit::Collection.new(XapitMember, "").last.should == @foo
end
-
+
it "should support page and per_page options" do
Xapit::Collection.new(XapitMember, :page => 1, :per_page => 1).should == [@hello]
Xapit::Collection.new(XapitMember, :page => 2, :per_page => 1).should == [@foo]
end
-
+
it "should have offset" do
Xapit::Collection.new(XapitMember, :page => 2, :per_page => 1).offset.should == 1
end
-
+
it "should have total_entries, total_pages, current_page, per_page, previous_page, next_page" do
collection = Xapit::Collection.new(XapitMember, "", :per_page => 1, :page => 2)
collection.total_entries.should == 2
@@ -66,93 +66,93 @@
collection.previous_page.should == 1
collection.next_page.should be_nil
end
-
+
it "should set xapit_relevance in results" do
results = Xapit::Collection.new(XapitMember, "")
results.each do |record|
record.xapit_relevance.class.should == Fixnum
end
end
-
+
it "should find nothing when searching unknown facet" do
Xapit::Collection.new(XapitMember, :facets => ["unknownfacet"]).should be_empty
end
-
+
it "should find matching facet" do
ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
Xapit::Collection.new(XapitMember, :facets => ids*2).should == [@hello]
end
-
+
it "should split facets string on dash" do
ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
Xapit::Collection.new(XapitMember, :facets => (ids*2).join("-")).should == [@hello]
end
-
+
it "should have one facet with two options with blank keywords" do
facets = Xapit::Collection.new(XapitMember, "").facets
facets.size.should == 1
facets.first.options.size.should == 2
end
-
+
it "should have no applied facets when there are no given facets" do
Xapit::Collection.new(XapitMember, "").applied_facet_options.should be_empty
end
-
+
it "should list applied facets" do
ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
results = Xapit::Collection.new(XapitMember, "", :facets => (ids*2).join("-"))
results.applied_facet_options.map(&:name).should == ["hello world", "hello world"]
end
-
+
it "should pass existing facet identifiers to applied options" do
ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
results = Xapit::Collection.new(XapitMember, "", :facets => (ids*2).join("-"))
results.applied_facet_options.first.existing_facet_identifiers.should == (ids*2)
end
-
+
it "should sort records in specified order" do
Xapit::Collection.new(XapitMember, :order => :name).should == [@foo, @hello]
end
-
+
it "should have no spelling suggestions for empty query" do
Xapit::Collection.new(XapitMember).spelling_suggestion.should == nil
end
-
+
it "should have no spelling suggestion for very different query" do
Xapit::Collection.new(XapitMember, "match nothing").spelling_suggestion.should == nil
end
-
+
it "should have spelling suggestion for single-word query" do
Xapit::Collection.new(XapitMember, "wrld").spelling_suggestion.should == "world"
end
-
+
it "should have spelling suggestion for multi-word query" do
Xapit::Collection.new(XapitMember, "helo bat wrld").spelling_suggestion.should == "hello bar world"
end
-
+
it "should raise error when fetching spelling suggestion if spelling is disabled" do
Xapit::Config.options[:spelling] = false
lambda { Xapit::Collection.new(XapitMember, "foo").spelling_suggestion }.should raise_error
end
-
+
it "should find similar records" do
member = XapitMember.new(:name => "foo bar world")
Xapit::Collection.search_similar(member).should == [@foo, @hello]
end
-
+
it "should be able to specify classes" do
Xapit::Collection.new(nil, "foo", :classes => [String, Array]).should == []
Xapit::Collection.new(nil, "foo", :classes => [String, Array, XapitMember]).should == [@foo]
end
-
+
it "should support nested or_search" do
Xapit::Collection.new(XapitMember, "world", :order => :name).or_search("foo").should == [@foo, @hello]
end
-
+
it "should override options in nested or_search" do
Xapit::Collection.new(XapitMember, "world", :order => :name, :per_page => 2).or_search("foo", :per_page => 1).should == [@foo]
end
-
+
it "should combine multiple or_search" do
@buz = XapitMember.new(:name => "buz")
@zot = XapitMember.new(:name => "zot")
@@ -160,14 +160,14 @@
Xapit.index_all
Xapit::Collection.new(XapitMember, "world", :order => :name).or_search("foo").or_search("buz").should == [@buz, @foo, @hello]
end
-
+
it "should support nested search" do
@zap = XapitMember.new(:name => "zap world")
Xapit.remove_database
Xapit.index_all
Xapit::Collection.new(XapitMember, "world").search("zap") == [@zap]
end
-
+
it "should inherit options in nested collection search" do
Xapit::Collection.new(XapitMember, "world", :per_page => 3).search("zap").per_page.should == 3
end
View
20 spec/xapit/config_spec.rb
@@ -5,52 +5,52 @@
Xapit::Config.database.writable_database.should be_kind_of(Xapian::WritableDatabase)
Xapit::Config.database.readable_database.should be_kind_of(Xapian::Database)
end
-
+
it "should default query parser to SimpleQueryParser" do
Xapit::Config.setup
Xapit::Config.query_parser.should == Xapit::ClassicQueryParser
end
-
+
it "should be able to set query parser on setup" do
Xapit::Config.setup(:query_parser => Xapit::SimpleQueryParser)
Xapit::Config.query_parser.should == Xapit::SimpleQueryParser
end
-
+
it "should default indexer to SimpleIndexer" do
Xapit::Config.setup
Xapit::Config.indexer.should == Xapit::SimpleIndexer
end
-
+
it "should be able to set indexer on setup" do
Xapit::Config.setup(:indexer => Xapit::ClassicIndexer)
Xapit::Config.indexer.should == Xapit::ClassicIndexer
end
-
+
it "should have spelling enabled by default" do
Xapit::Config.setup
Xapit::Config.spelling?.should be_true
end
-
+
it "should be able to specify spelling setting at setup" do
Xapit::Config.setup(:spelling => false)
Xapit::Config.spelling?.should be_false
end
-
+
it "should default stemming to english" do
Xapit::Config.setup
Xapit::Config.stemming == "english"
end
-
+
it "should be able to specify stemming setting at setup" do
Xapit::Config.setup(:stemming => "german")
Xapit::Config.stemming == "german"
end
-
+
it "should remove the database if it is a true xapian database" do
Xapit::Config.remove_database
File.exist?(Xapit::Config.path).should be_false
end
-
+
it "should NOT remove the database if it is not a xapian database" do
path = Xapit::Config.path + "/testing"
FileUtils.mkdir_p(path)
View
8 spec/xapit/facet_blueprint_spec.rb
@@ -8,20 +8,20 @@
facet1.identifiers_for("foo").should_not == facet1.identifiers_for("bar")
facet1.identifiers_for("foo").should_not == facet2.identifiers_for("foo")
end
-
+
it "should generate unique identifiers for each value returned" do
facet = Xapit::FacetBlueprint.new(XapitMember, 0, :to_a)
facet.identifiers_for(["foo", "bar"]).size.should == 2
end
-
+
it "should humanize attribute for name if one isn't given" do
Xapit::FacetBlueprint.new(XapitMember, 0, :visible).name.should == "Visible"
end
-
+
it "should use custom name if given" do
Xapit::FacetBlueprint.new(XapitMember, 0, :visible, "custom").name.should == "custom"
end
-
+
it "should not have identifiers for blank values" do
facet = Xapit::FacetBlueprint.new(XapitMember, 0, :to_a)
facet.identifiers_for(["", nil, "bar"]).size.should == 1
View
16 spec/xapit/facet_option_spec.rb
@@ -7,7 +7,7 @@
stub(option).identifier { "foo" }
option.to_param.should == "abc-123-foo"
end
-
+
it "should remove current identifier from previous identifiers if it exists" do
Xapit.setup(:breadcrumb_facets => false)
option = Xapit::FacetOption.new(nil, nil, nil)
@@ -15,7 +15,7 @@
stub(option).identifier { "foo" }
option.to_param.should == "abc-123"
end
-
+
it "should support breadcrumb style facets" do
Xapit.setup(:breadcrumb_facets => true)
option = Xapit::FacetOption.new(nil, nil, nil)
@@ -23,7 +23,7 @@
stub(option).identifier { "123" }
option.to_param.should == "abc-123"
end
-
+
describe "with database" do
before(:each) do
XapitMember.xapit do |index|
@@ -31,12 +31,12 @@
index.facet :category
end
end
-
+
it "should have identifier hashing name and value" do
option = Xapit::FacetOption.new("XapitMember", "age", "17")
option.identifier.should == "0c93ee1"
end
-
+
it "should find facet option from database given id" do
doc = Xapit::Document.new
doc.data = "XapitMember|||age|||17"
@@ -46,7 +46,7 @@
option.name.should == "17"
option.facet.name.should == "Person Age"
end
-
+
it "should save facet to database" do
Xapit::Config.database.writable_database # make sure there's a database setup in case we try to read from it
option = Xapit::FacetOption.new(nil, nil, nil)
@@ -55,7 +55,7 @@
option.save
Xapit::FacetOption.find(option.identifier).should_not be_nil
end
-
+
it "should not save facet if it already exists" do
doc = Xapit::Document.new
doc.data = "XapitMember|||age|||17"
@@ -66,7 +66,7 @@
stub(option).identifier { "abc123" }
option.save
end
-
+
it "should find facet option which doesn't have a value" do
doc = Xapit::Document.new
doc.data = "XapitMember|||age|||"
View
22 spec/xapit/facet_spec.rb
@@ -7,7 +7,7 @@
index.facet :visible
end
end
-
+
describe "indexed" do
before(:each) do
@visible1 = XapitMember.new(:visible => true)
@@ -15,54 +15,54 @@
@invisible = XapitMember.new(:visible => false)
Xapit.index_all
end
-
+
describe "facet from empty search" do
before(:each) do
@facet = XapitMember.search("").facets.first
end
-
+
it "should have the name of 'Visible'" do
@facet.name.should == 'Visible'
end
-
+
it "should have true and false options" do
@facet.options.map(&:name).sort.should == %w[false true]
end
-
+
it "should have record count" do
@facet.options.detect { |o| o.name == 'true' }.count.should == 2
@facet.options.detect { |o| o.name == 'false' }.count.should == 1
end
-
+
it "should have identifier for options" do
blueprint = Xapit::FacetBlueprint.new(XapitMember, 0, :visible)
@facet.options.detect { |o| o.name == 'true' }.identifier.should == blueprint.identifiers_for(@visible1).first
@facet.options.detect { |o| o.name == 'false' }.identifier.should == blueprint.identifiers_for(@invisible).first
end
-
+
it "should have matching identifiers" do
blueprint = Xapit::FacetBlueprint.new(XapitMember, 0, :visible)
hash = { blueprint.identifiers_for(@visible1).first => 2, blueprint.identifiers_for(@invisible).first => 1 }
@facet.matching_identifiers.should == hash
end
-
+
it "should not include matching identifiers that are current" do
blueprint = Xapit::FacetBlueprint.new(XapitMember, 0, :visible)
@facet.existing_facet_identifiers = blueprint.identifiers_for(@visible1)
@facet.matching_identifiers.should == { blueprint.identifiers_for(@invisible).first => 1 }
end
-
+
it "should return identifier on to_param" do
blueprint = Xapit::FacetBlueprint.new(XapitMember, 0, :visible)
@facet.options.detect { |o| o.name == 'true' }.to_param.should == blueprint.identifiers_for(@visible1).first
end
-
+
it "should sort options in alphabetical order" do
@facet.options.first.name.should == 'false'
@facet.options.last.name.should == 'true'
end
end
-
+
it "should not list facets if only one option is found" do
blueprint = Xapit::FacetBlueprint.new(XapitMember, 0, :visible)
facets = XapitMember.search(:facets => blueprint.identifiers_for(@visible1)).facets
View
28 spec/xapit/index_blueprint_spec.rb
@@ -5,7 +5,7 @@
XapitMember.xapit { } # call so methods are generated
@index = Xapit::IndexBlueprint.new(XapitMember)
end
-
+
it "should remember text attributes" do
@index.text(:foo)
@index.text(:bar, :blah)
@@ -14,65 +14,65 @@
@index.text_attributes[:foo][:proc].should be_nil
@index.text_attributes[:custom][:proc].should be_kind_of(Proc)
end
-
+
it "should remember field attributes" do
@index.field(:foo)
@index.field(:bar, :blah)
@index.field_attributes.should include(:foo, :bar, :blah)
end
-
+
it "should remember facets" do
@index.facet(:foo)
@index.facet(:bar, "Baz")
@index.facets.map(&:name).should == ["Foo", "Baz"]
end
-
+
it "should remember sortable attributes" do
@index.sortable(:foo)
@index.sortable(:bar, :blah)
@index.sortable_attributes.should include(:foo, :bar, :blah)
end
-
+
it "should have a sortable position offset by facets" do
@index.facet(:foo)
@index.facet(:test)
@index.sortable(:bar, :blah)