xapit 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/{CHANGELOG → CHANGELOG.rdoc} +7 -2
- data/Gemfile +19 -0
- data/LICENSE +4 -4
- data/README.rdoc +61 -108
- data/Rakefile +11 -10
- data/features/facets.feature +93 -82
- data/features/finding.feature +196 -138
- data/features/indexing.feature +35 -37
- data/features/remote_server.feature +10 -0
- data/features/step_definitions/xapit_steps.rb +53 -25
- data/features/suggestions.feature +20 -14
- data/features/support/env.rb +13 -6
- data/features/support/xapit_helpers.rb +8 -9
- data/lib/generators/xapit/install_generator.rb +14 -0
- data/lib/generators/xapit/templates/xapit.ru +6 -0
- data/lib/generators/xapit/templates/xapit.yml +11 -0
- data/lib/xapit.rb +106 -64
- data/lib/xapit/client/collection.rb +150 -0
- data/lib/xapit/client/facet.rb +11 -0
- data/lib/xapit/client/facet_option.rb +29 -0
- data/lib/xapit/client/index_builder.rb +67 -0
- data/lib/xapit/client/membership.rb +46 -0
- data/lib/xapit/client/model_adapters/abstract_model_adapter.rb +30 -0
- data/lib/xapit/client/model_adapters/active_record_adapter.rb +27 -0
- data/lib/xapit/client/model_adapters/default_model_adapter.rb +7 -0
- data/lib/xapit/client/railtie.rb +18 -0
- data/lib/xapit/client/remote_database.rb +21 -0
- data/lib/xapit/client/tasks.rb +18 -0
- data/lib/xapit/server/app.rb +27 -0
- data/lib/xapit/server/database.rb +47 -0
- data/lib/xapit/server/indexer.rb +138 -0
- data/lib/xapit/server/query.rb +240 -0
- data/spec/fixtures/blankdb/flintlock +0 -0
- data/spec/fixtures/blankdb/iamchert +1 -0
- data/spec/fixtures/blankdb/postlist.DB +0 -0
- data/spec/fixtures/blankdb/postlist.baseA +0 -0
- data/spec/fixtures/blankdb/record.DB +0 -0
- data/spec/fixtures/blankdb/record.baseA +0 -0
- data/spec/fixtures/blankdb/termlist.DB +0 -0
- data/spec/fixtures/blankdb/termlist.baseA +0 -0
- data/spec/fixtures/xapit.ru +13 -0
- data/spec/fixtures/xapit.yml +4 -0
- data/spec/spec_helper.rb +8 -9
- data/spec/support/spec_macros.rb +6 -0
- data/spec/{xapit_member.rb → support/xapit_member.rb} +14 -16
- data/spec/xapit/client/collection_spec.rb +63 -0
- data/spec/xapit/client/facet_option_spec.rb +26 -0
- data/spec/xapit/client/facet_spec.rb +13 -0
- data/spec/xapit/client/index_builder_spec.rb +66 -0
- data/spec/xapit/client/membership_spec.rb +43 -0
- data/spec/xapit/client/model_adapters/active_record_adapter_spec.rb +62 -0
- data/spec/xapit/client/model_adapters/default_model_adapter_spec.rb +7 -0
- data/spec/xapit/client/remote_database_spec.rb +19 -0
- data/spec/xapit/server/app_spec.rb +22 -0
- data/spec/xapit/server/database_spec.rb +37 -0
- data/spec/xapit/server/indexer_spec.rb +82 -0
- data/spec/xapit/server/query_spec.rb +43 -0
- data/spec/xapit/xapit_spec.rb +28 -0
- metadata +124 -93
- data/Manifest +0 -60
- data/features/sorting.feature +0 -29
- data/init.rb +0 -1
- data/install.rb +0 -8
- data/lib/xapit/adapters/abstract_adapter.rb +0 -47
- data/lib/xapit/adapters/active_record_adapter.rb +0 -20
- data/lib/xapit/adapters/data_mapper_adapter.rb +0 -10
- data/lib/xapit/collection.rb +0 -187
- data/lib/xapit/config.rb +0 -84
- data/lib/xapit/facet.rb +0 -67
- data/lib/xapit/facet_blueprint.rb +0 -59
- data/lib/xapit/facet_option.rb +0 -56
- data/lib/xapit/index_blueprint.rb +0 -147
- data/lib/xapit/indexers/abstract_indexer.rb +0 -116
- data/lib/xapit/indexers/classic_indexer.rb +0 -29
- data/lib/xapit/indexers/simple_indexer.rb +0 -38
- data/lib/xapit/membership.rb +0 -137
- data/lib/xapit/query.rb +0 -89
- data/lib/xapit/query_parsers/abstract_query_parser.rb +0 -174
- data/lib/xapit/query_parsers/classic_query_parser.rb +0 -29
- data/lib/xapit/query_parsers/simple_query_parser.rb +0 -75
- data/lib/xapit/rake_tasks.rb +0 -13
- data/rails_generators/xapit/USAGE +0 -13
- data/rails_generators/xapit/templates/setup_xapit.rb +0 -1
- data/rails_generators/xapit/templates/xapit.rake +0 -4
- data/rails_generators/xapit/xapit_generator.rb +0 -20
- data/spec/xapit/adapters/active_record_adapter_spec.rb +0 -31
- data/spec/xapit/adapters/data_mapper_adapter_spec.rb +0 -10
- data/spec/xapit/collection_spec.rb +0 -176
- data/spec/xapit/config_spec.rb +0 -62
- data/spec/xapit/facet_blueprint_spec.rb +0 -29
- data/spec/xapit/facet_option_spec.rb +0 -80
- data/spec/xapit/facet_spec.rb +0 -73
- data/spec/xapit/index_blueprint_spec.rb +0 -112
- data/spec/xapit/indexers/abstract_indexer_spec.rb +0 -111
- data/spec/xapit/indexers/classic_indexer_spec.rb +0 -35
- data/spec/xapit/indexers/simple_indexer_spec.rb +0 -69
- data/spec/xapit/membership_spec.rb +0 -55
- data/spec/xapit/query_parsers/abstract_query_parser_spec.rb +0 -60
- data/spec/xapit/query_parsers/classic_query_parser_spec.rb +0 -20
- data/spec/xapit/query_parsers/simple_query_parser_spec.rb +0 -86
- data/spec/xapit/query_spec.rb +0 -60
- data/tasks/spec.rb +0 -9
- data/tasks/xapit.rake +0 -1
- data/uninstall.rb +0 -5
- data/xapit.gemspec +0 -30
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class Facet
|
|
4
|
+
attr_reader :name, :options
|
|
5
|
+
def initialize(attribute, options, applied_facets = [])
|
|
6
|
+
@name = attribute.to_s.gsub("_", " ").gsub(/\b([a-z])/) { $1.to_s.upcase }
|
|
7
|
+
@options = options.map { |option| FacetOption.new(attribute, option, applied_facets) }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class FacetOption
|
|
4
|
+
attr_reader :count, :attribute
|
|
5
|
+
def initialize(attribute, option, applied_facets = [])
|
|
6
|
+
@attribute = attribute
|
|
7
|
+
@value = option[:value]
|
|
8
|
+
@count = option[:count].to_i
|
|
9
|
+
@applied_facets = applied_facets
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def identifier
|
|
13
|
+
Xapit.facet_identifier(@attribute, @value)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def name
|
|
17
|
+
@value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_param
|
|
21
|
+
if @applied_facets.include? identifier
|
|
22
|
+
(@applied_facets - [identifier]).join('-')
|
|
23
|
+
else
|
|
24
|
+
(@applied_facets + [identifier]).join("-")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class IndexBuilder
|
|
4
|
+
attr_reader :attributes
|
|
5
|
+
def initialize
|
|
6
|
+
@attributes = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def text(*args, &block)
|
|
10
|
+
add_attribute(:text, *args, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def field(*args, &block)
|
|
14
|
+
add_attribute(:field, *args, &block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def sortable(*args, &block)
|
|
18
|
+
add_attribute(:sortable, *args, &block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def facet(name, custom_name = nil, &block)
|
|
22
|
+
options = {}
|
|
23
|
+
options[:name] = custom_name if custom_name
|
|
24
|
+
add_attribute(:facet, name, options, &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_document(member)
|
|
28
|
+
Xapit.database.add_document(document_data(member))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def remove_document(member)
|
|
32
|
+
Xapit.database.remove_document(document_data(member))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update_document(member)
|
|
36
|
+
Xapit.database.update_document(document_data(member))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def document_data(member)
|
|
40
|
+
data = {:class => member.class.name, :id => member.id, :attributes => {}}
|
|
41
|
+
attributes.each do |name, options|
|
|
42
|
+
value = member.send(name)
|
|
43
|
+
value = options[:_block].call(value) if options[:_block]
|
|
44
|
+
data[:attributes][name] = options.merge(:value => value)
|
|
45
|
+
end
|
|
46
|
+
data
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def facets
|
|
50
|
+
attributes.keys.select do |attribute|
|
|
51
|
+
attributes[attribute][:facet]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def add_attribute(type, *args, &block)
|
|
58
|
+
options = args.last.kind_of?(Hash) ? args.pop : {}
|
|
59
|
+
args.each do |attribute|
|
|
60
|
+
@attributes[attribute] ||= {}
|
|
61
|
+
@attributes[attribute][type] = options
|
|
62
|
+
@attributes[attribute][:_block] = block if block
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
module Membership
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def xapit(&block)
|
|
10
|
+
@xapit_index_builder = IndexBuilder.new
|
|
11
|
+
@xapit_index_builder.instance_eval(&block)
|
|
12
|
+
include AdditionalMethods unless include?(AdditionalMethods)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module AdditionalMethods
|
|
17
|
+
def self.included(base)
|
|
18
|
+
base.extend ClassMethods
|
|
19
|
+
base.xapit_model_adapter.setup
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
def xapit_model_adapter
|
|
24
|
+
@xapit_model_adapter ||= Xapit::Client::AbstractModelAdapter.adapter_class(self).new(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def xapit_index_builder
|
|
28
|
+
@xapit_index_builder
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def xapit_search(*args)
|
|
32
|
+
Collection.new.in_classes(self).include_facets(*xapit_index_builder.facets).search(*args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def search(*args)
|
|
36
|
+
xapit_search(*args)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def search_similar(*args)
|
|
41
|
+
self.class.search(*args).similar_to(self)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class AbstractModelAdapter
|
|
4
|
+
def self.inherited(subclass)
|
|
5
|
+
@@subclasses ||= []
|
|
6
|
+
@@subclasses << subclass
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.adapter_class(model_class)
|
|
10
|
+
@@subclasses.detect { |subclass| subclass.for_class?(model_class) } || DefaultModelAdapter
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.for_class?(model_class)
|
|
14
|
+
false # override in subclass
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
# override in subclass
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def index_all
|
|
22
|
+
# override in subclass
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def initialize(model_class)
|
|
26
|
+
@model_class = model_class
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class ActiveRecordAdapter < AbstractModelAdapter
|
|
4
|
+
def self.for_class?(model_class)
|
|
5
|
+
model_class <= ActiveRecord::Base
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
@model_class.after_create do |member|
|
|
10
|
+
member.class.xapit_index_builder.add_document(member) if Xapit.config[:enabled]
|
|
11
|
+
end
|
|
12
|
+
@model_class.after_update do |member|
|
|
13
|
+
member.class.xapit_index_builder.update_document(member) if Xapit.config[:enabled]
|
|
14
|
+
end
|
|
15
|
+
@model_class.after_destroy do |member|
|
|
16
|
+
member.class.xapit_index_builder.remove_document(member) if Xapit.config[:enabled]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def index_all
|
|
21
|
+
@model_class.find_each do |member|
|
|
22
|
+
member.class.xapit_index_builder.add_document(member)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class Railtie < Rails::Railtie
|
|
4
|
+
initializer "xapit.config" do
|
|
5
|
+
path = Rails.root.join("config/xapit.yml")
|
|
6
|
+
Xapit.load_config(path, Rails.env) if path.exist?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
initializer "xapit.membership" do
|
|
10
|
+
ActiveRecord::Base.send(:include, Xapit::Client::Membership) if defined? ActiveRecord
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
rake_tasks do
|
|
14
|
+
load File.expand_path("../tasks.rb", __FILE__)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Client
|
|
3
|
+
class RemoteDatabase
|
|
4
|
+
def initialize(url)
|
|
5
|
+
@url = url
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
Xapit::Server::Database::COMMANDS.each do |command|
|
|
9
|
+
define_method(command) do |options|
|
|
10
|
+
request(command, options)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def request(command, options)
|
|
15
|
+
uri = URI.parse("#{@url}/xapit/#{command}")
|
|
16
|
+
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request_post(uri.path, options.to_json) }
|
|
17
|
+
Xapit.symbolize_keys(JSON.parse("[#{response.body}]").first) # terrible hack for handling simple objects
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "rack"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
namespace :xapit do
|
|
5
|
+
desc "Index all models for Xapit search"
|
|
6
|
+
task :index => :environment do
|
|
7
|
+
raise "No Xapian database specified in config." if Xapit.config[:database_path].blank?
|
|
8
|
+
FileUtils.rm_rf("tmp/xapit") if File.exist? "tmp/xapit"
|
|
9
|
+
FileUtils.mv(Xapit.config[:database_path], "tmp/xapit") if File.exist? Xapit.config[:database_path]
|
|
10
|
+
models = ActiveRecord::Base.subclasses
|
|
11
|
+
Dir[Rails.root.join("app", "models", "**", "*.rb")].each do |file|
|
|
12
|
+
# I hate to rescue nil, maybe there's a better way to handle unknown constants
|
|
13
|
+
models << File.basename(file, ".*").classify.constantize rescue nil
|
|
14
|
+
end
|
|
15
|
+
xapit_models = models.compact.uniq.select { |m| m.respond_to? :xapit_model_adapter }
|
|
16
|
+
Xapit.index(*xapit_models)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Server
|
|
3
|
+
class App
|
|
4
|
+
def call(env)
|
|
5
|
+
request = Rack::Request.new(env)
|
|
6
|
+
command = request.path[%r</xapit/(.+)>, 1]
|
|
7
|
+
if Database::COMMANDS.include? command
|
|
8
|
+
action(command, request.body.gets)
|
|
9
|
+
else
|
|
10
|
+
render :status => 404
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def action(command, json)
|
|
15
|
+
data = Xapit.symbolize_keys(JSON.parse(json))
|
|
16
|
+
render :content => Xapit.database.send(command, data).to_json
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render(options = {})
|
|
20
|
+
options[:status] ||= 200
|
|
21
|
+
options[:content] ||= ""
|
|
22
|
+
options[:content_type] ||= "text/html"
|
|
23
|
+
[options[:status], {"Content-Type" => options[:content_type]}, [options[:content]]]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Server
|
|
3
|
+
class Database
|
|
4
|
+
COMMANDS = %w[query add_document remove_document update_document spelling_suggestion]
|
|
5
|
+
|
|
6
|
+
def initialize(path)
|
|
7
|
+
@path = path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def xapian_database
|
|
11
|
+
@xapian_database ||= load_database
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def add_document(data)
|
|
15
|
+
xapian_database.add_document(Indexer.new(data).document)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def remove_document(data)
|
|
19
|
+
xapian_database.delete_document(Indexer.new(data).id_term)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_document(data)
|
|
23
|
+
indexer = Indexer.new(data)
|
|
24
|
+
xapian_database.replace_document(indexer.id_term, indexer.document)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def query(data)
|
|
28
|
+
Query.new(data).data
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def spelling_suggestion(data)
|
|
32
|
+
Query.new(data).spelling_suggestion
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def load_database
|
|
38
|
+
if @path
|
|
39
|
+
FileUtils.mkdir_p(File.dirname(@path)) unless File.exist?(File.dirname(@path))
|
|
40
|
+
Xapian::WritableDatabase.new(@path, Xapian::DB_CREATE_OR_OPEN)
|
|
41
|
+
else
|
|
42
|
+
Xapian.inmemory_open
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module Xapit
|
|
2
|
+
module Server
|
|
3
|
+
class Indexer
|
|
4
|
+
def initialize(data)
|
|
5
|
+
@data = data
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def database
|
|
9
|
+
Xapit.database.xapian_database
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def document
|
|
13
|
+
document = Xapian::Document.new
|
|
14
|
+
document.data = id
|
|
15
|
+
terms.each { |term, weight| document.add_term(term, weight) }
|
|
16
|
+
text_terms.each { |term, weight| database.add_spelling(term, weight) } if Xapit.config[:spelling]
|
|
17
|
+
values.each { |index, value| document.add_value(index, value) }
|
|
18
|
+
save_facets
|
|
19
|
+
document
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def id
|
|
23
|
+
"#{@data[:class]}-#{@data[:id]}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def id_term
|
|
27
|
+
"Q#{id}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def terms
|
|
31
|
+
base_terms + text_terms + stemmed_text_terms + field_terms + facet_terms
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def values
|
|
35
|
+
values = {}
|
|
36
|
+
each_value do |index, value|
|
|
37
|
+
if values[index]
|
|
38
|
+
values[index] += "\3#{value}" # multiple values are split back out on the query side
|
|
39
|
+
else
|
|
40
|
+
values[index] = value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
values
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def text_terms
|
|
47
|
+
each_attribute(:text) do |name, value, options|
|
|
48
|
+
value.to_s.split(/\s+/u).map { |w| w.gsub(/[^\w]/u, "") }.map(&:downcase).map do |term|
|
|
49
|
+
[term, options[:weight] || 1]
|
|
50
|
+
end
|
|
51
|
+
end.flatten(1)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def stemmed_text_terms
|
|
55
|
+
if stemmer
|
|
56
|
+
each_attribute(:text) do |name, value, options|
|
|
57
|
+
value.to_s.split(/\s+/u).map { |w| w.gsub(/[^\w]/u, "") }.map(&:downcase).map do |term|
|
|
58
|
+
["Z#{stemmer.call(term)}", options[:weight] || 1]
|
|
59
|
+
end
|
|
60
|
+
end.flatten(1)
|
|
61
|
+
else
|
|
62
|
+
[]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def field_terms
|
|
67
|
+
each_attribute(:field) do |name, value, options|
|
|
68
|
+
["X#{name}-#{parse_field(value)}", 1]
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def facet_terms
|
|
73
|
+
each_attribute(:facet) do |name, value, options|
|
|
74
|
+
["F#{Xapit.facet_identifier(name, value)}", 1]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def save_facets
|
|
79
|
+
each_attribute(:facet) do |name, value, options|
|
|
80
|
+
id = Xapit.facet_identifier(name, value)
|
|
81
|
+
unless database.term_exists("Xid-#{id}")
|
|
82
|
+
document = Xapian::Document.new
|
|
83
|
+
document.data = "#{name}|||#{value}"
|
|
84
|
+
document.add_term("CFacetOption")
|
|
85
|
+
document.add_term("Xid-#{id}")
|
|
86
|
+
database.add_document(document)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def stemmer
|
|
94
|
+
@stemmer ||= Xapian::Stem.new(Xapit.config[:stemming]) if Xapit.config[:stemming]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def base_terms
|
|
98
|
+
[["C#{@data[:class]}", 1], [id_term, 1]]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_field(value)
|
|
102
|
+
if value.kind_of? Time
|
|
103
|
+
value.to_i
|
|
104
|
+
else
|
|
105
|
+
value.to_s.downcase
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def each_value
|
|
110
|
+
each_attribute(:field) do |name, value, options|
|
|
111
|
+
yield(Xapit.value_index(:field, name), Xapit.serialize_value(value))
|
|
112
|
+
end
|
|
113
|
+
each_attribute(:sortable) do |name, value, options|
|
|
114
|
+
yield(Xapit.value_index(:sortable, name), Xapit.serialize_value(value))
|
|
115
|
+
end
|
|
116
|
+
each_attribute(:facet) do |name, value, options|
|
|
117
|
+
yield(Xapit.value_index(:facet, name), value)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def each_attribute(type)
|
|
122
|
+
if @data[:attributes]
|
|
123
|
+
@data[:attributes].map do |name, options|
|
|
124
|
+
if options.has_key? type
|
|
125
|
+
if options[:value].kind_of? Array
|
|
126
|
+
options[:value].map { |value| yield(name, value, options[type]) }
|
|
127
|
+
else
|
|
128
|
+
[yield(name, options[:value], options[type])]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end.compact.flatten(1)
|
|
132
|
+
else
|
|
133
|
+
[]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|