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.
Files changed (156) hide show
  1. data/CHANGE_LOG +239 -0
  2. data/LICENSE +19 -0
  3. data/README.markdown +118 -0
  4. data/README.rdoc +107 -0
  5. data/Rakefile +99 -0
  6. data/TESTING_THE_PLUGIN +25 -0
  7. data/VERSION.yml +4 -0
  8. data/config/solr.yml +15 -0
  9. data/config/solr_environment.rb +32 -0
  10. data/lib/acts_as_solr.rb +65 -0
  11. data/lib/acts_as_solr/acts_methods.rb +352 -0
  12. data/lib/acts_as_solr/class_methods.rb +236 -0
  13. data/lib/acts_as_solr/common_methods.rb +89 -0
  14. data/lib/acts_as_solr/deprecation.rb +61 -0
  15. data/lib/acts_as_solr/instance_methods.rb +165 -0
  16. data/lib/acts_as_solr/lazy_document.rb +18 -0
  17. data/lib/acts_as_solr/parser_methods.rb +203 -0
  18. data/lib/acts_as_solr/search_results.rb +68 -0
  19. data/lib/acts_as_solr/solr_fixtures.rb +13 -0
  20. data/lib/acts_as_solr/tasks.rb +10 -0
  21. data/lib/acts_as_solr/tasks/database.rake +16 -0
  22. data/lib/acts_as_solr/tasks/solr.rake +135 -0
  23. data/lib/acts_as_solr/tasks/test.rake +5 -0
  24. data/lib/solr.rb +26 -0
  25. data/lib/solr/connection.rb +177 -0
  26. data/lib/solr/document.rb +75 -0
  27. data/lib/solr/exception.rb +13 -0
  28. data/lib/solr/field.rb +36 -0
  29. data/lib/solr/importer.rb +19 -0
  30. data/lib/solr/importer/array_mapper.rb +26 -0
  31. data/lib/solr/importer/delimited_file_source.rb +38 -0
  32. data/lib/solr/importer/hpricot_mapper.rb +27 -0
  33. data/lib/solr/importer/mapper.rb +51 -0
  34. data/lib/solr/importer/solr_source.rb +41 -0
  35. data/lib/solr/importer/xpath_mapper.rb +35 -0
  36. data/lib/solr/indexer.rb +52 -0
  37. data/lib/solr/request.rb +26 -0
  38. data/lib/solr/request/add_document.rb +58 -0
  39. data/lib/solr/request/base.rb +36 -0
  40. data/lib/solr/request/commit.rb +29 -0
  41. data/lib/solr/request/delete.rb +48 -0
  42. data/lib/solr/request/dismax.rb +46 -0
  43. data/lib/solr/request/index_info.rb +22 -0
  44. data/lib/solr/request/modify_document.rb +46 -0
  45. data/lib/solr/request/optimize.rb +19 -0
  46. data/lib/solr/request/ping.rb +36 -0
  47. data/lib/solr/request/select.rb +54 -0
  48. data/lib/solr/request/spellcheck.rb +30 -0
  49. data/lib/solr/request/standard.rb +402 -0
  50. data/lib/solr/request/update.rb +23 -0
  51. data/lib/solr/response.rb +27 -0
  52. data/lib/solr/response/add_document.rb +17 -0
  53. data/lib/solr/response/base.rb +42 -0
  54. data/lib/solr/response/commit.rb +15 -0
  55. data/lib/solr/response/delete.rb +13 -0
  56. data/lib/solr/response/dismax.rb +8 -0
  57. data/lib/solr/response/index_info.rb +26 -0
  58. data/lib/solr/response/modify_document.rb +17 -0
  59. data/lib/solr/response/optimize.rb +14 -0
  60. data/lib/solr/response/ping.rb +26 -0
  61. data/lib/solr/response/ruby.rb +42 -0
  62. data/lib/solr/response/select.rb +17 -0
  63. data/lib/solr/response/spellcheck.rb +20 -0
  64. data/lib/solr/response/standard.rb +60 -0
  65. data/lib/solr/response/xml.rb +39 -0
  66. data/lib/solr/solrtasks.rb +27 -0
  67. data/lib/solr/util.rb +32 -0
  68. data/lib/solr/xml.rb +44 -0
  69. data/solr/CHANGES.txt +1207 -0
  70. data/solr/LICENSE.txt +712 -0
  71. data/solr/NOTICE.txt +90 -0
  72. data/solr/etc/jetty.xml +205 -0
  73. data/solr/etc/webdefault.xml +379 -0
  74. data/solr/lib/easymock.jar +0 -0
  75. data/solr/lib/jetty-6.1.3.jar +0 -0
  76. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  77. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  78. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  79. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  80. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  81. data/solr/lib/servlet-api-2.4.jar +0 -0
  82. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  83. data/solr/lib/xpp3-1.1.3.4.O.jar +0 -0
  84. data/solr/solr/README.txt +52 -0
  85. data/solr/solr/bin/abc +176 -0
  86. data/solr/solr/bin/abo +176 -0
  87. data/solr/solr/bin/backup +108 -0
  88. data/solr/solr/bin/backupcleaner +142 -0
  89. data/solr/solr/bin/commit +128 -0
  90. data/solr/solr/bin/optimize +129 -0
  91. data/solr/solr/bin/readercycle +129 -0
  92. data/solr/solr/bin/rsyncd-disable +77 -0
  93. data/solr/solr/bin/rsyncd-enable +76 -0
  94. data/solr/solr/bin/rsyncd-start +145 -0
  95. data/solr/solr/bin/rsyncd-stop +105 -0
  96. data/solr/solr/bin/scripts-util +83 -0
  97. data/solr/solr/bin/snapcleaner +148 -0
  98. data/solr/solr/bin/snapinstaller +168 -0
  99. data/solr/solr/bin/snappuller +248 -0
  100. data/solr/solr/bin/snappuller-disable +77 -0
  101. data/solr/solr/bin/snappuller-enable +77 -0
  102. data/solr/solr/bin/snapshooter +109 -0
  103. data/solr/solr/conf/admin-extra.html +31 -0
  104. data/solr/solr/conf/protwords.txt +21 -0
  105. data/solr/solr/conf/schema.xml +126 -0
  106. data/solr/solr/conf/scripts.conf +24 -0
  107. data/solr/solr/conf/solrconfig.xml +458 -0
  108. data/solr/solr/conf/stopwords.txt +57 -0
  109. data/solr/solr/conf/synonyms.txt +31 -0
  110. data/solr/solr/conf/xslt/example.xsl +132 -0
  111. data/solr/solr/conf/xslt/example_atom.xsl +63 -0
  112. data/solr/solr/conf/xslt/example_rss.xsl +62 -0
  113. data/solr/start.jar +0 -0
  114. data/solr/webapps/solr.war +0 -0
  115. data/test/config/solr.yml +2 -0
  116. data/test/db/connections/mysql/connection.rb +10 -0
  117. data/test/db/connections/sqlite/connection.rb +8 -0
  118. data/test/db/migrate/001_create_books.rb +15 -0
  119. data/test/db/migrate/002_create_movies.rb +12 -0
  120. data/test/db/migrate/003_create_categories.rb +11 -0
  121. data/test/db/migrate/004_create_electronics.rb +16 -0
  122. data/test/db/migrate/005_create_authors.rb +12 -0
  123. data/test/db/migrate/006_create_postings.rb +9 -0
  124. data/test/db/migrate/007_create_posts.rb +13 -0
  125. data/test/db/migrate/008_create_gadgets.rb +11 -0
  126. data/test/fixtures/authors.yml +9 -0
  127. data/test/fixtures/books.yml +13 -0
  128. data/test/fixtures/categories.yml +7 -0
  129. data/test/fixtures/db_definitions/mysql.sql +41 -0
  130. data/test/fixtures/electronics.yml +49 -0
  131. data/test/fixtures/movies.yml +9 -0
  132. data/test/fixtures/postings.yml +10 -0
  133. data/test/functional/acts_as_solr_test.rb +413 -0
  134. data/test/functional/association_indexing_test.rb +37 -0
  135. data/test/functional/faceted_search_test.rb +163 -0
  136. data/test/functional/multi_solr_search_test.rb +57 -0
  137. data/test/models/author.rb +10 -0
  138. data/test/models/book.rb +10 -0
  139. data/test/models/category.rb +8 -0
  140. data/test/models/electronic.rb +25 -0
  141. data/test/models/gadget.rb +9 -0
  142. data/test/models/movie.rb +17 -0
  143. data/test/models/novel.rb +2 -0
  144. data/test/models/post.rb +3 -0
  145. data/test/models/posting.rb +11 -0
  146. data/test/test_helper.rb +54 -0
  147. data/test/unit/acts_methods_shoulda.rb +68 -0
  148. data/test/unit/class_methods_shoulda.rb +85 -0
  149. data/test/unit/common_methods_shoulda.rb +111 -0
  150. data/test/unit/instance_methods_shoulda.rb +318 -0
  151. data/test/unit/lazy_document_shoulda.rb +34 -0
  152. data/test/unit/parser_instance.rb +19 -0
  153. data/test/unit/parser_methods_shoulda.rb +268 -0
  154. data/test/unit/solr_instance.rb +49 -0
  155. data/test/unit/test_helper.rb +24 -0
  156. 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