muck-solr 0.4.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/CHANGE_LOG +239 -0
- data/LICENSE +19 -0
- data/README.markdown +118 -0
- data/README.rdoc +107 -0
- data/Rakefile +99 -0
- data/TESTING_THE_PLUGIN +25 -0
- data/VERSION.yml +4 -0
- data/config/solr.yml +15 -0
- data/config/solr_environment.rb +32 -0
- data/lib/acts_as_solr.rb +65 -0
- data/lib/acts_as_solr/acts_methods.rb +352 -0
- data/lib/acts_as_solr/class_methods.rb +236 -0
- data/lib/acts_as_solr/common_methods.rb +89 -0
- data/lib/acts_as_solr/deprecation.rb +61 -0
- data/lib/acts_as_solr/instance_methods.rb +165 -0
- data/lib/acts_as_solr/lazy_document.rb +18 -0
- data/lib/acts_as_solr/parser_methods.rb +203 -0
- data/lib/acts_as_solr/search_results.rb +68 -0
- data/lib/acts_as_solr/solr_fixtures.rb +13 -0
- data/lib/acts_as_solr/tasks.rb +10 -0
- data/lib/acts_as_solr/tasks/database.rake +16 -0
- data/lib/acts_as_solr/tasks/solr.rake +135 -0
- data/lib/acts_as_solr/tasks/test.rake +5 -0
- data/lib/solr.rb +26 -0
- data/lib/solr/connection.rb +177 -0
- data/lib/solr/document.rb +75 -0
- data/lib/solr/exception.rb +13 -0
- data/lib/solr/field.rb +36 -0
- data/lib/solr/importer.rb +19 -0
- data/lib/solr/importer/array_mapper.rb +26 -0
- data/lib/solr/importer/delimited_file_source.rb +38 -0
- data/lib/solr/importer/hpricot_mapper.rb +27 -0
- data/lib/solr/importer/mapper.rb +51 -0
- data/lib/solr/importer/solr_source.rb +41 -0
- data/lib/solr/importer/xpath_mapper.rb +35 -0
- data/lib/solr/indexer.rb +52 -0
- data/lib/solr/request.rb +26 -0
- data/lib/solr/request/add_document.rb +58 -0
- data/lib/solr/request/base.rb +36 -0
- data/lib/solr/request/commit.rb +29 -0
- data/lib/solr/request/delete.rb +48 -0
- data/lib/solr/request/dismax.rb +46 -0
- data/lib/solr/request/index_info.rb +22 -0
- data/lib/solr/request/modify_document.rb +46 -0
- data/lib/solr/request/optimize.rb +19 -0
- data/lib/solr/request/ping.rb +36 -0
- data/lib/solr/request/select.rb +54 -0
- data/lib/solr/request/spellcheck.rb +30 -0
- data/lib/solr/request/standard.rb +402 -0
- data/lib/solr/request/update.rb +23 -0
- data/lib/solr/response.rb +27 -0
- data/lib/solr/response/add_document.rb +17 -0
- data/lib/solr/response/base.rb +42 -0
- data/lib/solr/response/commit.rb +15 -0
- data/lib/solr/response/delete.rb +13 -0
- data/lib/solr/response/dismax.rb +8 -0
- data/lib/solr/response/index_info.rb +26 -0
- data/lib/solr/response/modify_document.rb +17 -0
- data/lib/solr/response/optimize.rb +14 -0
- data/lib/solr/response/ping.rb +26 -0
- data/lib/solr/response/ruby.rb +42 -0
- data/lib/solr/response/select.rb +17 -0
- data/lib/solr/response/spellcheck.rb +20 -0
- data/lib/solr/response/standard.rb +60 -0
- data/lib/solr/response/xml.rb +39 -0
- data/lib/solr/solrtasks.rb +27 -0
- data/lib/solr/util.rb +32 -0
- data/lib/solr/xml.rb +44 -0
- data/solr/CHANGES.txt +1207 -0
- data/solr/LICENSE.txt +712 -0
- data/solr/NOTICE.txt +90 -0
- data/solr/etc/jetty.xml +205 -0
- data/solr/etc/webdefault.xml +379 -0
- data/solr/lib/easymock.jar +0 -0
- data/solr/lib/jetty-6.1.3.jar +0 -0
- data/solr/lib/jetty-util-6.1.3.jar +0 -0
- data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
- data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
- data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
- data/solr/lib/servlet-api-2.4.jar +0 -0
- data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
- data/solr/lib/xpp3-1.1.3.4.O.jar +0 -0
- data/solr/solr/README.txt +52 -0
- data/solr/solr/bin/abc +176 -0
- data/solr/solr/bin/abo +176 -0
- data/solr/solr/bin/backup +108 -0
- data/solr/solr/bin/backupcleaner +142 -0
- data/solr/solr/bin/commit +128 -0
- data/solr/solr/bin/optimize +129 -0
- data/solr/solr/bin/readercycle +129 -0
- data/solr/solr/bin/rsyncd-disable +77 -0
- data/solr/solr/bin/rsyncd-enable +76 -0
- data/solr/solr/bin/rsyncd-start +145 -0
- data/solr/solr/bin/rsyncd-stop +105 -0
- data/solr/solr/bin/scripts-util +83 -0
- data/solr/solr/bin/snapcleaner +148 -0
- data/solr/solr/bin/snapinstaller +168 -0
- data/solr/solr/bin/snappuller +248 -0
- data/solr/solr/bin/snappuller-disable +77 -0
- data/solr/solr/bin/snappuller-enable +77 -0
- data/solr/solr/bin/snapshooter +109 -0
- data/solr/solr/conf/admin-extra.html +31 -0
- data/solr/solr/conf/protwords.txt +21 -0
- data/solr/solr/conf/schema.xml +126 -0
- data/solr/solr/conf/scripts.conf +24 -0
- data/solr/solr/conf/solrconfig.xml +458 -0
- data/solr/solr/conf/stopwords.txt +57 -0
- data/solr/solr/conf/synonyms.txt +31 -0
- data/solr/solr/conf/xslt/example.xsl +132 -0
- data/solr/solr/conf/xslt/example_atom.xsl +63 -0
- data/solr/solr/conf/xslt/example_rss.xsl +62 -0
- data/solr/start.jar +0 -0
- data/solr/webapps/solr.war +0 -0
- data/test/config/solr.yml +2 -0
- data/test/db/connections/mysql/connection.rb +10 -0
- data/test/db/connections/sqlite/connection.rb +8 -0
- data/test/db/migrate/001_create_books.rb +15 -0
- data/test/db/migrate/002_create_movies.rb +12 -0
- data/test/db/migrate/003_create_categories.rb +11 -0
- data/test/db/migrate/004_create_electronics.rb +16 -0
- data/test/db/migrate/005_create_authors.rb +12 -0
- data/test/db/migrate/006_create_postings.rb +9 -0
- data/test/db/migrate/007_create_posts.rb +13 -0
- data/test/db/migrate/008_create_gadgets.rb +11 -0
- data/test/fixtures/authors.yml +9 -0
- data/test/fixtures/books.yml +13 -0
- data/test/fixtures/categories.yml +7 -0
- data/test/fixtures/db_definitions/mysql.sql +41 -0
- data/test/fixtures/electronics.yml +49 -0
- data/test/fixtures/movies.yml +9 -0
- data/test/fixtures/postings.yml +10 -0
- data/test/functional/acts_as_solr_test.rb +413 -0
- data/test/functional/association_indexing_test.rb +37 -0
- data/test/functional/faceted_search_test.rb +163 -0
- data/test/functional/multi_solr_search_test.rb +57 -0
- data/test/models/author.rb +10 -0
- data/test/models/book.rb +10 -0
- data/test/models/category.rb +8 -0
- data/test/models/electronic.rb +25 -0
- data/test/models/gadget.rb +9 -0
- data/test/models/movie.rb +17 -0
- data/test/models/novel.rb +2 -0
- data/test/models/post.rb +3 -0
- data/test/models/posting.rb +11 -0
- data/test/test_helper.rb +54 -0
- data/test/unit/acts_methods_shoulda.rb +68 -0
- data/test/unit/class_methods_shoulda.rb +85 -0
- data/test/unit/common_methods_shoulda.rb +111 -0
- data/test/unit/instance_methods_shoulda.rb +318 -0
- data/test/unit/lazy_document_shoulda.rb +34 -0
- data/test/unit/parser_instance.rb +19 -0
- data/test/unit/parser_methods_shoulda.rb +268 -0
- data/test/unit/solr_instance.rb +49 -0
- data/test/unit/test_helper.rb +24 -0
- metadata +241 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
module ActsAsSolr #:nodoc:
|
|
2
|
+
|
|
3
|
+
module ClassMethods
|
|
4
|
+
include CommonMethods
|
|
5
|
+
include ParserMethods
|
|
6
|
+
|
|
7
|
+
# Finds instances of a model. Terms are ANDed by default, can be overwritten
|
|
8
|
+
# by using OR between terms
|
|
9
|
+
#
|
|
10
|
+
# Here's a sample (untested) code for your controller:
|
|
11
|
+
#
|
|
12
|
+
# def search
|
|
13
|
+
# results = Book.find_by_solr params[:query]
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# You can also search for specific fields by searching for 'field:value'
|
|
17
|
+
#
|
|
18
|
+
# ====options:
|
|
19
|
+
# offset:: - The first document to be retrieved (offset)
|
|
20
|
+
# limit:: - The number of rows per page
|
|
21
|
+
# order:: - Orders (sort by) the result set using a given criteria:
|
|
22
|
+
#
|
|
23
|
+
# Book.find_by_solr 'ruby', :order => 'description asc'
|
|
24
|
+
#
|
|
25
|
+
# field_types:: This option is deprecated and will be obsolete by version 1.0.
|
|
26
|
+
# There's no need to specify the :field_types anymore when doing a
|
|
27
|
+
# search in a model that specifies a field type for a field. The field
|
|
28
|
+
# types are automatically traced back when they're included.
|
|
29
|
+
#
|
|
30
|
+
# class Electronic < ActiveRecord::Base
|
|
31
|
+
# acts_as_solr :fields => [{:price => :range_float}]
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# facets:: This option argument accepts the following arguments:
|
|
35
|
+
# fields:: The fields to be included in the faceted search (Solr's facet.field)
|
|
36
|
+
# query:: The queries to be included in the faceted search (Solr's facet.query)
|
|
37
|
+
# zeros:: Display facets with count of zero. (true|false)
|
|
38
|
+
# sort:: Sorts the faceted resuls by highest to lowest count. (true|false)
|
|
39
|
+
# browse:: This is where the 'drill-down' of the facets work. Accepts an array of
|
|
40
|
+
# fields in the format "facet_field:term"
|
|
41
|
+
# mincount:: Replacement for zeros (it has been deprecated in Solr). Specifies the
|
|
42
|
+
# minimum count necessary for a facet field to be returned. (Solr's
|
|
43
|
+
# facet.mincount) Overrides :zeros if it is specified. Default is 0.
|
|
44
|
+
#
|
|
45
|
+
# dates:: Run date faceted queries using the following arguments:
|
|
46
|
+
# fields:: The fields to be included in the faceted date search (Solr's facet.date).
|
|
47
|
+
# It may be either a String/Symbol or Hash. If it's a hash the options are the
|
|
48
|
+
# same as date_facets minus the fields option (i.e., :start:, :end, :gap, :other,
|
|
49
|
+
# :between). These options if provided will override the base options.
|
|
50
|
+
# (Solr's f.<field_name>.date.<key>=<value>).
|
|
51
|
+
# start:: The lower bound for the first date range for all Date Faceting. Required if
|
|
52
|
+
# :fields is present
|
|
53
|
+
# end:: The upper bound for the last date range for all Date Faceting. Required if
|
|
54
|
+
# :fields is prsent
|
|
55
|
+
# gap:: The size of each date range expressed as an interval to be added to the lower
|
|
56
|
+
# bound using the DateMathParser syntax. Required if :fields is prsent
|
|
57
|
+
# hardend:: A Boolean parameter instructing Solr what do do in the event that
|
|
58
|
+
# facet.date.gap does not divide evenly between facet.date.start and facet.date.end.
|
|
59
|
+
# other:: This param indicates that in addition to the counts for each date range
|
|
60
|
+
# constraint between facet.date.start and facet.date.end, other counds should be
|
|
61
|
+
# calculated. May specify more then one in an Array. The possible options are:
|
|
62
|
+
# before:: - all records with lower bound less than start
|
|
63
|
+
# after:: - all records with upper bound greater than end
|
|
64
|
+
# between:: - all records with field values between start and end
|
|
65
|
+
# none:: - compute no other bounds (useful in per field assignment)
|
|
66
|
+
# all:: - shortcut for before, after, and between
|
|
67
|
+
# filter:: Similar to :query option provided by :facets, in that accepts an array of
|
|
68
|
+
# of date queries to limit results. Can not be used as a part of a :field hash.
|
|
69
|
+
# This is the only option that can be used if :fields is not present.
|
|
70
|
+
#
|
|
71
|
+
# Example:
|
|
72
|
+
#
|
|
73
|
+
# Electronic.find_by_solr "memory", :facets => {:zeros => false, :sort => true,
|
|
74
|
+
# :query => ["price:[* TO 200]",
|
|
75
|
+
# "price:[200 TO 500]",
|
|
76
|
+
# "price:[500 TO *]"],
|
|
77
|
+
# :fields => [:category, :manufacturer],
|
|
78
|
+
# :browse => ["category:Memory","manufacturer:Someone"]}
|
|
79
|
+
#
|
|
80
|
+
#
|
|
81
|
+
# Examples of date faceting:
|
|
82
|
+
#
|
|
83
|
+
# basic:
|
|
84
|
+
# Electronic.find_by_solr "memory", :facets => {:dates => {:fields => [:updated_at, :created_at],
|
|
85
|
+
# :start => 'NOW-10YEARS/DAY', :end => 'NOW/DAY', :gap => '+2YEARS', :other => :before}}
|
|
86
|
+
#
|
|
87
|
+
# advanced:
|
|
88
|
+
# Electronic.find_by_solr "memory", :facets => {:dates => {:fields => [:updated_at,
|
|
89
|
+
# {:created_at => {:start => 'NOW-20YEARS/DAY', :end => 'NOW-10YEARS/DAY', :other => [:before, :after]}
|
|
90
|
+
# }], :start => 'NOW-10YEARS/DAY', :end => 'NOW/DAY', :other => :before, :filter =>
|
|
91
|
+
# ["created_at:[NOW-10YEARS/DAY TO NOW/DAY]", "updated_at:[NOW-1YEAR/DAY TO NOW/DAY]"]}}
|
|
92
|
+
#
|
|
93
|
+
# filter only:
|
|
94
|
+
# Electronic.find_by_solr "memory", :facets => {:dates => {:filter => "updated_at:[NOW-1YEAR/DAY TO NOW/DAY]"}}
|
|
95
|
+
#
|
|
96
|
+
#
|
|
97
|
+
#
|
|
98
|
+
# scores:: If set to true this will return the score as a 'solr_score' attribute
|
|
99
|
+
# for each one of the instances found. Does not currently work with find_id_by_solr
|
|
100
|
+
#
|
|
101
|
+
# books = Book.find_by_solr 'ruby OR splinter', :scores => true
|
|
102
|
+
# books.records.first.solr_score
|
|
103
|
+
# => 1.21321397
|
|
104
|
+
# books.records.last.solr_score
|
|
105
|
+
# => 0.12321548
|
|
106
|
+
#
|
|
107
|
+
# lazy:: If set to true the search will return objects that will touch the database when you ask for one
|
|
108
|
+
# of their attributes for the first time. Useful when you're using fragment caching based solely on
|
|
109
|
+
# types and ids.
|
|
110
|
+
#
|
|
111
|
+
def find_by_solr(query, options={})
|
|
112
|
+
data = parse_query(query, options)
|
|
113
|
+
return parse_results(data, options)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Finds instances of a model and returns an array with the ids:
|
|
117
|
+
# Book.find_id_by_solr "rails" => [1,4,7]
|
|
118
|
+
# The options accepted are the same as find_by_solr
|
|
119
|
+
#
|
|
120
|
+
def find_id_by_solr(query, options={})
|
|
121
|
+
data = parse_query(query, options)
|
|
122
|
+
return parse_results(data, {:format => :ids})
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# This method can be used to execute a search across multiple models:
|
|
126
|
+
# Book.multi_solr_search "Napoleon OR Tom", :models => [Movie]
|
|
127
|
+
#
|
|
128
|
+
# ====options:
|
|
129
|
+
# Accepts the same options as find_by_solr plus:
|
|
130
|
+
# models:: The additional models you'd like to include in the search
|
|
131
|
+
# results_format:: Specify the format of the results found
|
|
132
|
+
# :objects :: Will return an array with the results being objects (default). Example:
|
|
133
|
+
# Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :objects
|
|
134
|
+
# :ids :: Will return an array with the ids of each entry found. Example:
|
|
135
|
+
# Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :ids
|
|
136
|
+
# => [{"id" => "Movie:1"},{"id" => Book:1}]
|
|
137
|
+
# Where the value of each array is as Model:instance_id
|
|
138
|
+
# scores:: If set to true this will return the score as a 'solr_score' attribute
|
|
139
|
+
# for each one of the instances found. Does not currently work with find_id_by_solr
|
|
140
|
+
#
|
|
141
|
+
# books = Book.multi_solr_search 'ruby OR splinter', :scores => true
|
|
142
|
+
# books.records.first.solr_score
|
|
143
|
+
# => 1.21321397
|
|
144
|
+
# books.records.last.solr_score
|
|
145
|
+
# => 0.12321548
|
|
146
|
+
#
|
|
147
|
+
def multi_solr_search(query, options = {})
|
|
148
|
+
models = multi_model_suffix(options)
|
|
149
|
+
options.update(:results_format => :objects) unless options[:results_format]
|
|
150
|
+
data = parse_query(query, options, models)
|
|
151
|
+
|
|
152
|
+
if data.nil? or data.total_hits == 0
|
|
153
|
+
return SearchResults.new(:docs => [], :total => 0)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
result = find_multi_search_objects(data, options)
|
|
157
|
+
if options[:scores] and options[:results_format] == :objects
|
|
158
|
+
add_scores(result, data)
|
|
159
|
+
end
|
|
160
|
+
SearchResults.new :docs => result, :total => data.total_hits
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def find_multi_search_objects(data, options)
|
|
164
|
+
result = []
|
|
165
|
+
if options[:results_format] == :objects
|
|
166
|
+
data.hits.each do |doc|
|
|
167
|
+
k = doc.fetch('id').first.to_s.split(':')
|
|
168
|
+
result << k[0].constantize.find_by_id(k[1])
|
|
169
|
+
end
|
|
170
|
+
elsif options[:results_format] == :ids
|
|
171
|
+
data.hits.each{|doc| result << {"id" => doc["id"].to_s}}
|
|
172
|
+
end
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def multi_model_suffix(options)
|
|
177
|
+
models = "AND (#{solr_configuration[:type_field]}:#{self.name}"
|
|
178
|
+
models << " OR " + options[:models].collect {|m| "#{solr_configuration[:type_field]}:" + m.to_s}.join(" OR ") if options[:models].is_a?(Array)
|
|
179
|
+
models << ")"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# returns the total number of documents found in the query specified:
|
|
183
|
+
# Book.count_by_solr 'rails' => 3
|
|
184
|
+
#
|
|
185
|
+
def count_by_solr(query, options = {})
|
|
186
|
+
data = parse_query(query, options)
|
|
187
|
+
data.total_hits
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# It's used to rebuild the Solr index for a specific model.
|
|
191
|
+
# Book.rebuild_solr_index
|
|
192
|
+
#
|
|
193
|
+
# If batch_size is greater than 0, adds will be done in batches.
|
|
194
|
+
# NOTE: If using sqlserver, be sure to use a finder with an explicit order.
|
|
195
|
+
# Non-edge versions of rails do not handle pagination correctly for sqlserver
|
|
196
|
+
# without an order clause.
|
|
197
|
+
#
|
|
198
|
+
# If a finder block is given, it will be called to retrieve the items to index.
|
|
199
|
+
# This can be very useful for things such as updating based on conditions or
|
|
200
|
+
# using eager loading for indexed associations.
|
|
201
|
+
def rebuild_solr_index(batch_size=0, &finder)
|
|
202
|
+
finder ||= lambda { |ar, options| ar.find(:all, options.merge({:order => self.primary_key})) }
|
|
203
|
+
start_time = Time.now
|
|
204
|
+
|
|
205
|
+
if batch_size > 0
|
|
206
|
+
items_processed = 0
|
|
207
|
+
limit = batch_size
|
|
208
|
+
offset = 0
|
|
209
|
+
begin
|
|
210
|
+
iteration_start = Time.now
|
|
211
|
+
items = finder.call(self, {:limit => limit, :offset => offset})
|
|
212
|
+
add_batch = items.collect { |content| content.to_solr_doc }
|
|
213
|
+
|
|
214
|
+
if items.size > 0
|
|
215
|
+
solr_add add_batch
|
|
216
|
+
solr_commit
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
items_processed += items.size
|
|
220
|
+
last_id = items.last.id if items.last
|
|
221
|
+
time_so_far = Time.now - start_time
|
|
222
|
+
iteration_time = Time.now - iteration_start
|
|
223
|
+
logger.info "#{Process.pid}: #{items_processed} items for #{self.name} have been batch added to index in #{'%.3f' % time_so_far}s at #{'%.3f' % (items_processed / time_so_far)} items/sec (#{'%.3f' % (items.size / iteration_time)} items/sec for the last batch). Last id: #{last_id}"
|
|
224
|
+
offset += items.size
|
|
225
|
+
end while items.nil? || items.size > 0
|
|
226
|
+
else
|
|
227
|
+
items = finder.call(self, {})
|
|
228
|
+
items.each { |content| content.solr_save }
|
|
229
|
+
items_processed = items.size
|
|
230
|
+
end
|
|
231
|
+
solr_optimize
|
|
232
|
+
logger.info items_processed > 0 ? "Index for #{self.name} has been rebuilt" : "Nothing to index for #{self.name}"
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module ActsAsSolr #:nodoc:
|
|
2
|
+
|
|
3
|
+
module CommonMethods
|
|
4
|
+
|
|
5
|
+
# Converts field types into Solr types
|
|
6
|
+
def get_solr_field_type(field_type)
|
|
7
|
+
if field_type.is_a?(Symbol)
|
|
8
|
+
case field_type
|
|
9
|
+
when :float
|
|
10
|
+
return "f"
|
|
11
|
+
when :integer
|
|
12
|
+
return "i"
|
|
13
|
+
when :boolean
|
|
14
|
+
return "b"
|
|
15
|
+
when :string
|
|
16
|
+
return "s"
|
|
17
|
+
when :date
|
|
18
|
+
return "d"
|
|
19
|
+
when :range_float
|
|
20
|
+
return "rf"
|
|
21
|
+
when :range_integer
|
|
22
|
+
return "ri"
|
|
23
|
+
when :facet
|
|
24
|
+
return "facet"
|
|
25
|
+
when :text
|
|
26
|
+
return "t"
|
|
27
|
+
else
|
|
28
|
+
raise "Unknown field_type symbol: #{field_type}"
|
|
29
|
+
end
|
|
30
|
+
elsif field_type.is_a?(String)
|
|
31
|
+
return field_type
|
|
32
|
+
else
|
|
33
|
+
raise "Unknown field_type class: #{field_type.class}: #{field_type}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Sets a default value when value being set is nil.
|
|
38
|
+
def set_value_if_nil(field_type)
|
|
39
|
+
case field_type
|
|
40
|
+
when "b", :boolean
|
|
41
|
+
return "false"
|
|
42
|
+
when "s", "t", "d", :date, :string, :text
|
|
43
|
+
return ""
|
|
44
|
+
when "f", "rf", :float, :range_float
|
|
45
|
+
return 0.00
|
|
46
|
+
when "i", "ri", :integer, :range_integer
|
|
47
|
+
return 0
|
|
48
|
+
else
|
|
49
|
+
return ""
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sends an add command to Solr
|
|
54
|
+
def solr_add(add_xml)
|
|
55
|
+
ActsAsSolr::Post.execute(Solr::Request::AddDocument.new(add_xml))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Sends the delete command to Solr
|
|
59
|
+
def solr_delete(solr_ids)
|
|
60
|
+
ActsAsSolr::Post.execute(Solr::Request::Delete.new(:id => solr_ids))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Sends the commit command to Solr
|
|
64
|
+
def solr_commit
|
|
65
|
+
ActsAsSolr::Post.execute(Solr::Request::Commit.new)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Optimizes the Solr index. Solr says:
|
|
69
|
+
#
|
|
70
|
+
# Optimizations can take nearly ten minutes to run.
|
|
71
|
+
# We are presuming optimizations should be run once following large
|
|
72
|
+
# batch-like updates to the collection and/or once a day.
|
|
73
|
+
#
|
|
74
|
+
# One of the solutions for this would be to create a cron job that
|
|
75
|
+
# runs every day at midnight and optmizes the index:
|
|
76
|
+
# 0 0 * * * /your_rails_dir/script/runner -e production "Model.solr_optimize"
|
|
77
|
+
#
|
|
78
|
+
def solr_optimize
|
|
79
|
+
ActsAsSolr::Post.execute(Solr::Request::Optimize.new)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns the id for the given instance
|
|
83
|
+
def record_id(object)
|
|
84
|
+
eval "object.#{object.class.primary_key}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module ActsAsSolr #:nodoc:
|
|
2
|
+
|
|
3
|
+
class Post
|
|
4
|
+
def initialize(body, mode = :search)
|
|
5
|
+
@body = body
|
|
6
|
+
@mode = mode
|
|
7
|
+
puts "The method ActsAsSolr::Post.new(body, mode).execute_post is depracated. " +
|
|
8
|
+
"Use ActsAsSolr::Post.execute(body, mode) instead!"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def execute_post
|
|
12
|
+
ActsAsSolr::Post.execute(@body, @mode)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def find_with_facet(query, options={})
|
|
18
|
+
Deprecation.plog "The method find_with_facet is deprecated. Use find_by_solr instead, passing the " +
|
|
19
|
+
"arguments the same way you used to do with find_with_facet."
|
|
20
|
+
find_by_solr(query, options)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Deprecation
|
|
25
|
+
# Validates the options passed during query
|
|
26
|
+
def self.validate_query options={}
|
|
27
|
+
if options[:field_types]
|
|
28
|
+
plog "The option :field_types for searching is deprecated. " +
|
|
29
|
+
"The field types are automatically traced back when you specify a field type in your model."
|
|
30
|
+
end
|
|
31
|
+
if options[:sort_by]
|
|
32
|
+
plog "The option :sort_by is deprecated, use :order instead!"
|
|
33
|
+
options[:order] ||= options[:sort_by]
|
|
34
|
+
end
|
|
35
|
+
if options[:start]
|
|
36
|
+
plog "The option :start is deprecated, use :offset instead!"
|
|
37
|
+
options[:offset] ||= options[:start]
|
|
38
|
+
end
|
|
39
|
+
if options[:rows]
|
|
40
|
+
plog "The option :rows is deprecated, use :limit instead!"
|
|
41
|
+
options[:limit] ||= options[:rows]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Validates the options passed during indexing
|
|
46
|
+
def self.validate_index options={}
|
|
47
|
+
if options[:background]
|
|
48
|
+
plog "The :background option is being deprecated. There are better and more efficient " +
|
|
49
|
+
"ways to handle delayed saving of your records."
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# This will print the text to stdout and log the text
|
|
54
|
+
# if rails logger is available
|
|
55
|
+
def self.plog text
|
|
56
|
+
puts text
|
|
57
|
+
RAILS_DEFAULT_LOGGER.warn text if defined? RAILS_DEFAULT_LOGGER
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
module ActsAsSolr #:nodoc:
|
|
2
|
+
|
|
3
|
+
module InstanceMethods
|
|
4
|
+
|
|
5
|
+
# Solr id is <class.name>:<id> to be unique across all models
|
|
6
|
+
def solr_id
|
|
7
|
+
"#{self.class.name}:#{record_id(self)}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# saves to the Solr index
|
|
11
|
+
def solr_save
|
|
12
|
+
return true if indexing_disabled?
|
|
13
|
+
if evaluate_condition(:if, self)
|
|
14
|
+
logger.debug "solr_save: #{self.class.name} : #{record_id(self)}"
|
|
15
|
+
solr_add to_solr_doc
|
|
16
|
+
solr_commit if configuration[:auto_commit]
|
|
17
|
+
true
|
|
18
|
+
else
|
|
19
|
+
solr_destroy
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def indexing_disabled?
|
|
24
|
+
evaluate_condition(:offline, self) || !configuration[:if]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# remove from index
|
|
28
|
+
def solr_destroy
|
|
29
|
+
return true if indexing_disabled?
|
|
30
|
+
logger.debug "solr_destroy: #{self.class.name} : #{record_id(self)}"
|
|
31
|
+
solr_delete solr_id
|
|
32
|
+
solr_commit if configuration[:auto_commit]
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# convert instance to Solr document
|
|
37
|
+
def to_solr_doc
|
|
38
|
+
logger.debug "to_solr_doc: creating doc for class: #{self.class.name}, id: #{record_id(self)}"
|
|
39
|
+
doc = Solr::Document.new
|
|
40
|
+
doc.boost = validate_boost(configuration[:boost]) if configuration[:boost]
|
|
41
|
+
|
|
42
|
+
doc << {:id => solr_id,
|
|
43
|
+
solr_configuration[:type_field] => self.class.name,
|
|
44
|
+
solr_configuration[:primary_key_field] => record_id(self).to_s}
|
|
45
|
+
|
|
46
|
+
# iterate through the fields and add them to the document,
|
|
47
|
+
configuration[:solr_fields].each do |field_name, options|
|
|
48
|
+
#field_type = configuration[:facets] && configuration[:facets].include?(field) ? :facet : :text
|
|
49
|
+
|
|
50
|
+
field_boost = options[:boost] || solr_configuration[:default_boost]
|
|
51
|
+
field_type = get_solr_field_type(options[:type])
|
|
52
|
+
solr_name = options[:as] || field_name
|
|
53
|
+
|
|
54
|
+
value = self.send("#{field_name}_for_solr")
|
|
55
|
+
value = set_value_if_nil(field_type) if value.to_s == ""
|
|
56
|
+
|
|
57
|
+
# add the field to the document, but only if it's not the id field
|
|
58
|
+
# or the type field (from single table inheritance), since these
|
|
59
|
+
# fields have already been added above.
|
|
60
|
+
if field_name.to_s != self.class.primary_key and field_name.to_s != "type"
|
|
61
|
+
suffix = get_solr_field_type(field_type)
|
|
62
|
+
# This next line ensures that e.g. nil dates are excluded from the
|
|
63
|
+
# document, since they choke Solr. Also ignores e.g. empty strings,
|
|
64
|
+
# but these can't be searched for anyway:
|
|
65
|
+
# http://www.mail-archive.com/solr-dev@lucene.apache.org/msg05423.html
|
|
66
|
+
next if value.nil? || value.to_s.strip.empty?
|
|
67
|
+
[value].flatten.each do |v|
|
|
68
|
+
v = set_value_if_nil(suffix) if value.to_s == ""
|
|
69
|
+
field = Solr::Field.new("#{solr_name}_#{suffix}" => ERB::Util.html_escape(v.to_s))
|
|
70
|
+
field.boost = validate_boost(field_boost)
|
|
71
|
+
doc << field
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
add_includes(doc)
|
|
77
|
+
logger.debug doc.to_xml
|
|
78
|
+
doc
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
def add_includes(doc)
|
|
83
|
+
if configuration[:solr_includes].respond_to?(:each)
|
|
84
|
+
configuration[:solr_includes].each do |association, options|
|
|
85
|
+
data = options[:multivalued] ? [] : ""
|
|
86
|
+
field_name = options[:as] || association.to_s.singularize
|
|
87
|
+
field_type = get_solr_field_type(options[:type])
|
|
88
|
+
field_boost = options[:boost] || solr_configuration[:default_boost]
|
|
89
|
+
suffix = get_solr_field_type(field_type)
|
|
90
|
+
case self.class.reflect_on_association(association).macro
|
|
91
|
+
when :has_many, :has_and_belongs_to_many
|
|
92
|
+
records = self.send(association).to_a
|
|
93
|
+
unless records.empty?
|
|
94
|
+
records.each {|r| data << include_value(r, options)}
|
|
95
|
+
[data].flatten.each do |value|
|
|
96
|
+
field = Solr::Field.new("#{field_name}_#{suffix}" => value)
|
|
97
|
+
field.boost = validate_boost(field_boost)
|
|
98
|
+
doc << field
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
when :has_one, :belongs_to
|
|
102
|
+
record = self.send(association)
|
|
103
|
+
unless record.nil?
|
|
104
|
+
doc["#{field_name}_#{suffix}"] = include_value(record, options)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def include_value(record, options)
|
|
112
|
+
if options[:using].is_a? Proc
|
|
113
|
+
options[:using].call(record)
|
|
114
|
+
elsif options[:using].is_a? Symbol
|
|
115
|
+
record.send(options[:using])
|
|
116
|
+
else
|
|
117
|
+
record.attributes.inject([]){|k,v| k << "#{v.first}=#{ERB::Util.html_escape(v.last)}"}.join(" ")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def validate_boost(boost)
|
|
122
|
+
boost_value = case boost
|
|
123
|
+
when Float
|
|
124
|
+
return solr_configuration[:default_boost] if boost < 0
|
|
125
|
+
boost
|
|
126
|
+
when Proc
|
|
127
|
+
boost.call(self)
|
|
128
|
+
when Symbol
|
|
129
|
+
if self.respond_to?(boost)
|
|
130
|
+
self.send(boost)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
boost_value || solr_configuration[:default_boost]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def condition_block?(condition)
|
|
138
|
+
condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def evaluate_condition(which_condition, field)
|
|
142
|
+
condition = configuration[which_condition]
|
|
143
|
+
case condition
|
|
144
|
+
when Symbol
|
|
145
|
+
field.send(condition)
|
|
146
|
+
when String
|
|
147
|
+
eval(condition, binding)
|
|
148
|
+
when FalseClass, NilClass
|
|
149
|
+
false
|
|
150
|
+
when TrueClass
|
|
151
|
+
true
|
|
152
|
+
else
|
|
153
|
+
if condition_block?(condition)
|
|
154
|
+
condition.call(field)
|
|
155
|
+
else
|
|
156
|
+
raise(
|
|
157
|
+
ArgumentError,
|
|
158
|
+
"The :#{which_condition} option has to be either a symbol, string (to be eval'ed), proc/method, true/false, or " +
|
|
159
|
+
"class implementing a static validation method"
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|