Commit 7fc1e86ff560e889865851efb51480d7def4aec7

Authored by Braulio Bhavamitra
1 parent da943ef5

Add acts_as_solr (excluding internal solr)

Showing 120 changed files with 7003 additions and 0 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 120 files displayed.

vendor/plugins/acts_as_solr/.gitignore 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +*.log
  2 +*.log
  3 +*_pid
  4 +coverage/*
  5 +coverage.data
  6 +solr/solr/data/*
  7 +.svn
  8 +test/db/*.db
... ...
vendor/plugins/acts_as_solr/CHANGE_LOG 0 → 100644
... ... @@ -0,0 +1,233 @@
  1 +== CHANGE_LOG
  2 +=== Development
  3 +<b>NEW</b>:: A unit test suite based on Shoulda, not requiring a running Solr or a Rails environment. (Mathias Meyer)
  4 +<b>NEW</b>:: Added the :offline option to the acts_as_solr method. (Mathias Meyer)
  5 +<b>NEW</b>:: Added :lazy flag to find_by_solr, and with that support to load records lazily. (Mathias Meyer)
  6 +<b>FIX</b>:: Added test: to solr.yml so you don't need to do this by hand. (Ken Harris)
  7 +<b>FIX</b>:: Updated bundled Solr to 1.3. (Mathias Meyer)
  8 +<b>FIX</b>:: Updated bundled solr-ruby to 0.0.6 which comes bundled with Solr 1.3. (Mathias Meyer)
  9 +<b>FIX</b>:: Improved logging of the reindex rake task. (David Stevenson)
  10 +<b>FIX</b>:: Added requirement for libxml-ruby > 0.7, if libxml-ruby is installed. (David Stevenson)
  11 +<b>FIX</b>:: Ruby 1.9 compatibility fixes. (David Palm)
  12 +<b>FIX</b>:: Fixed compatibility with Desert by renaming environment.rb to solr_environment.rb. (David Stevenson)
  13 +<b>FIX</b>:: Moved the require of solr_environment only into tasks that need it. (David Stevenson)
  14 +<b>NEW</b>:: Added support to alias fields and includes using the option :as for acts_as_solr. See the RDoc for details. (Nick Hengeveld)
  15 +<b>NEW</b>:: Added support to attach the score to multi search results. (Mathias Meyer)
  16 +<b>NEW</b>:: Added support for facets on date fields. (Curtis Hatter)
  17 +
  18 +=== 06-18-2007: Version 0.9
  19 +<b>NEW</b>:: Added the option :scores when doing a search. If set to true this will return the score as a 'solr_score' attribute or each one of the instances found
  20 + books = Book.find_by_solr 'ruby OR splinter', :scores => true
  21 + books.records.first.solr_score
  22 + => 1.21321397
  23 + books.records.last.solr_score
  24 + => 0.12321548
  25 +
  26 +<b>NEW</b>:: Major change on the way the results returned are accessed.
  27 + books = Book.find_by_solr 'ruby'
  28 + # the above will return a SearchResults class with 4 methods:
  29 + # docs|results|records: will return an array of records found
  30 + #
  31 + # books.records.is_a?(Array)
  32 + # => true
  33 + #
  34 + # total|num_found|total_hits: will return the total number of records found
  35 + #
  36 + # books.total
  37 + # => 2
  38 + #
  39 + # facets: will return the facets when doing a faceted search
  40 + #
  41 + # max_score|highest_score: returns the highest score found
  42 + #
  43 + # books.max_score
  44 + # => 1.3213213
  45 +
  46 +<b>NEW</b>:: Integrating acts_as_solr to use solr-ruby as the 'backend'. Integration based on the patch submitted by Erik Hatcher
  47 +<b>NEW</b>:: Re-factoring rebuild_solr_index to allow adds to be done in batch; and if a finder block is given, it will be called to retrieve the items to index. (thanks Daniel E.)
  48 +<b>NEW</b>:: Adding the option to specify the port Solr should start when using rake solr:start
  49 + rake solr:start RAILS_ENV=your_env PORT=XX
  50 +
  51 +<b>NEW</b>:: Adding deprecation warning for the :background configuration option. It will no longer be updated.
  52 +<b>NEW</b>:: Adding support for models that use a primary key other than integer
  53 + class Posting < ActiveRecord::Base
  54 + set_primary_key 'guid' #string
  55 + #make sure you set the :primary_key_field => 'pk_s' if you wish to use a string field as the primary key
  56 + acts_as_solr({},{:primary_key_field => 'pk_s'})
  57 + end
  58 +
  59 +<b>FIX</b>:: Disabling of storing most fields. Storage isn't useful for acts_as_solr in any field other than the pk and id fields. It just takes up space and time. (thanks Daniel E.)
  60 +<b>FIX</b>:: Re-factoring code submitted by Daniel E.
  61 +<b>NEW</b>:: Adding an :auto_commit option that will only send the commit command to Solr if it is set to true
  62 + class Author < ActiveRecord::Base
  63 + acts_as_solr :auto_commit => false
  64 + end
  65 +
  66 +<b>FIX</b>:: Fixing bug on rake's test task
  67 +<b>FIX</b>:: Making acts_as_solr's Post class compatible with Solr 1.2 (thanks Si)
  68 +<b>NEW</b>:: Adding Solr 1.2
  69 +<b>FIX</b>:: Removing Solr 1.1
  70 +<b>NEW</b>:: Adding a conditional :if option to the acts_as_solr call. It behaves the same way ActiveRecord's :if argument option does.
  71 + class Electronic < ActiveRecord::Base
  72 + acts_as_solr :if => proc{|record| record.is_active?}
  73 + end
  74 +
  75 +<b>NEW</b>:: Adding fixtures to Solr index when using rake db:fixtures:load
  76 +<b>FIX</b>:: Fixing boost warning messages
  77 +<b>FIX</b>:: Fixing bug when adding a facet to a field that contains boost
  78 +<b>NEW</b>:: Deprecating find_with_facet and combining functionality with find_by_solr
  79 +<b>NEW</b>:: Adding the option to :exclude_fields when indexing a model
  80 + class User < ActiveRecord::Base
  81 + acts_as_solr :exclude_fields => [:password, :login, :credit_card_number]
  82 + end
  83 +
  84 +<b>FIX</b>:: Fixing branch bug on older ruby version
  85 +<b>NEW</b>:: Adding boost support for fields and documents being indexed:
  86 + class Electronic < ActiveRecord::Base
  87 + # You can add boosting on a per-field basis or on the entire document
  88 + acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => 5.0
  89 + end
  90 +
  91 +<b>FIX</b>:: Fixed the acts_as_solr limitation to only accept test|development|production environments.
  92 +
  93 +=== 05-16-2007: Version 0.8.5
  94 +<b>FIX</b>:: There's no need to specify the :field_types anymore when doing a search in a model that specifies a field type for a field.
  95 +<b>FIX</b>:: Better handling of nil values from indexed fields. Solr complained when indexing fields with field type and the field values being passed as nils.
  96 +<b>NEW</b>:: Adding Solr sort (order by) option to the search query (thanks Kevin Hunt)
  97 +<b>FIX</b>:: Applying patch suggested for increasing the Solr commit speed (thanks Mourad Hammiche)
  98 +<b>FIX</b>:: Updated documentation
  99 +
  100 +=== 05-10-2007: Version 0.8
  101 +<b>NEW</b>: New video tutorial
  102 +<b>NEW</b>: Faceted search has been implemented and its possible to 'drill-down' on the facets
  103 +<b>NEW</b>: New rake tasks you can use to start/stop the solr server in test, development and production environments: (thanks Matt Clark)
  104 + rake solr:start|stop RAILS_ENV=test|development|production (defaults to development if none given)
  105 +
  106 +<b>NEW</b>: Changes to the plugin's test framework and it now supports Sqlite as well (thanks Matt Clark)
  107 +<b>FIX</b>: Patch applied (thanks Micah) that allows one to have multiple solr instances in the same servlet
  108 +<b>FIX</b>: Patch applied (thanks Micah) that allows indexing of STIs
  109 +<b>FIX</b>: Patch applied (thanks Gordon) that allows the plugin to use a table's primary key different than 'id'
  110 +<b>FIX</b>: Returning empty array instead of empty strings when no records are found
  111 +<b>FIX</b>: Problem with unit tests failing due to order of the tests and speed of the commits
  112 +
  113 +=== 02-16-2007: Version 0.7
  114 +<b>NEW</b>: You can now specify the field types when indexing and searching if
  115 +you'd like to preserve its original type:
  116 +
  117 +<b>Indexing</b>
  118 +
  119 +Each field passed can also be a hash with the value being a field type
  120 +
  121 + class Electronic < ActiveRecord::Base
  122 + acts_as_solr :fields => [{:price => :range_float}, {:current_time => :date}]
  123 + def current_time
  124 + Time.now
  125 + end
  126 + end
  127 +
  128 +<b>Searching</b>
  129 + Electronic.find_by_solr "ipod AND price:[* TO 59.99]",
  130 + :field_types => [{:price => :range_float}]
  131 +
  132 +The field types accepted are:
  133 +<em>:float</em>:: Index the field value as a float (ie.: 12.87)
  134 +<em>:integer</em>:: Index the field value as an integer (ie.: 31)
  135 +<em>:boolean</em>:: Index the field value as a boolean (ie.: true/false)
  136 +<em>:date</em>:: Index the field value as a date (ie.: Wed Nov 15 23:13:03 PST 2006)
  137 +<em>:string</em>:: Index the field value as a text string, not applying the same indexing filters as a regular text field
  138 +<em>:range_integer</em>:: Index the field value for integer range queries (ie.:[5 TO 20])
  139 +<em>:range_float</em>:: Index the field value for float range queries (ie.:[14.56 TO 19.99])
  140 +
  141 +<b>Setting the field type preserves its original type when indexed</b>
  142 +
  143 +<b>FIX</b>: Fixing sorting bug. Thanks for the catch Laurel
  144 +
  145 +<b>FIX</b>: Fixing small bug when installing the plugin
  146 +
  147 +<b>NEW</b>: Adding the :additional_fields option to the acts_as_solr method
  148 +
  149 +=== 02-05-2007: Version 0.6.5
  150 +<b>NEW</b>:: Added multi-model search, which can be used to execute a search across multiple models:
  151 + Book.multi_solr_search "Napoleon OR Tom", :models => [Movie]
  152 +
  153 +====options:
  154 +Accepts the same options as find_by_solr plus:
  155 +models:: The additional models you'd like to include in the search
  156 +results_format:: Specify the format of the results found
  157 + :objects :: Will return an array with the results being objects (default). Example:
  158 + Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :objects
  159 + :ids :: Will return an array with the ids of each entry found. Example:
  160 + Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :ids
  161 + => [{"id" => "Movie:1"},{"id" => Book:1}]
  162 + Where the value of each array is as Model:instance_id
  163 +
  164 +=== 02-03-2007: Version 0.6
  165 +<b>NEW</b>:: Added basic faceted search functionality for indexing and searching:
  166 +
  167 +==== Indexing:
  168 +
  169 + class Electronic < ActiveRecord::Base
  170 + acts_as_solr :facets => [:category, :manufacturer]
  171 + end
  172 +
  173 +==== Searching:
  174 +
  175 + Electronic.find_with_facet "memory", :facets => {:fields =>[:category]}
  176 +
  177 +=== 01-15-2007: Version 0.5
  178 +<b>NEW</b>:: Added model association indexing, which means you can include any :has_one, :has_many,
  179 +:belongs_to and :has_and_belongs_to_many association to be indexed:
  180 +
  181 + class Category < ActiveRecord::Base
  182 + has_many :books
  183 + acts_as_solr :include => [:books]
  184 + end
  185 +
  186 + class Book < ActiveRecord::Base
  187 + belongs_to :category
  188 + acts_as_solr :include => [:category]
  189 + end
  190 +
  191 +=== 01-11-2007:
  192 +<b>NEW</b>:: Added the acts_as_solr's plugin tests
  193 +
  194 +=== 11-07-2006: Version 0.4
  195 +<b>NEW</b>:: Added :background option, which takes and integer value (in minutes) to wait before committing the changes to Solr. This depends on rail_cron being installed. By setting up the background job we prevent the users from having to wait for Solr records to be created, and we keep from updating the index over and over for quickly successive changes. (Rob Kaufman)
  196 +
  197 +=== 11-02-2006: Version 0.3
  198 +<b>NEW</b>:: Added a method (Model.count_by_solr) that returns the total number of documents found based on query passed
  199 +<b>NEW</b>:: Added configuration for production and development environments
  200 +
  201 +=== 10-21-2006: Version 0.2
  202 +<b>PLUGIN</b>
  203 +<b>FIX</b>:: Fixed bug when mixing search-by-field and 'free' search: Model.find_by_solr 'solr AND name:Thiago'
  204 +<b>FIX</b>:: Fixed bug with multi-terms search: Book.find_by_solr 'anteater john'
  205 +<b>FIX</b>:: Fixed bug when including more than one search field: Model.find_by_solr 'name:Thiago AND engine:Solr'
  206 +<b>FIX</b>:: Fixed bug when rebuilding the index, it wasn't saving the data
  207 +<b>NEW</b>:: Added the ability to index custom methods from a model as search fields
  208 +<b>NEW</b>:: Added a search method (Model.find_id_by_solr) that will return only the id of the results
  209 +
  210 +<b>SCHEMA.XML</b>
  211 +<b>NEW</b>:: Added a new field: <field name="default" type="text" indexed="true" stored="true" />
  212 +<b>NEW</b>:: Added a default search field: <defaultSearchField>default</defaultSearchField>
  213 +<b>FIX</b>:: Changed the defaultOperator to AND instead of OR
  214 +
  215 +=== 09-29-2006: Version 0.1
  216 +<b>PLUGIN</b>
  217 +<b>NEW</b>:: Included the option of having a Solr config file inside the rails env.
  218 +<b>NEW</b>:: Added the ability of indexing only certain fields, if you chose to.
  219 +<b>NEW</b>:: Added configuration options
  220 +<b>NEW</b>:: Changed the way the search was done:
  221 + Old: You were forced the specify the field you wanted to look for
  222 + ('field:value') and you had to specify a default search field as
  223 + well, for when you didn't include the 'field' in the search term
  224 + New: The new search features include:
  225 + - You don't have to specify a default search field;
  226 + - You are not forced to include the field name in the search term,
  227 + unless you choose to search for a specific field ('name:Thiago');
  228 + - You can pass the starting row and the number of rows per page,
  229 + which is usefull for pagination
  230 +<b>NEW</b>:: Included a method to rebuild the index files
  231 +
  232 +<b>SCHEMA.XML</b>
  233 +<b>NEW</b>:: Created an optimized version of the config file to better work with this plugin
0 234 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/LICENSE 0 → 100644
... ... @@ -0,0 +1,19 @@
  1 +Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to deal
  5 +in the Software without restriction, including without limitation the rights
  6 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:
  9 +
  10 +The above copyright notice and this permission notice shall be included in all
  11 +copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +SOFTWARE.
0 20 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/README.markdown 0 → 100644
... ... @@ -0,0 +1,94 @@
  1 +`acts_as_solr` Rails plugin
  2 +======
  3 +This plugin adds full text search capabilities and many other nifty features from Apache's [Solr](http://lucene.apache.org/solr/) to any Rails model.
  4 +It was based on the first draft by Erik Hatcher.
  5 +
  6 +Current Release
  7 +======
  8 +The current stable release is v0.9 and was released on 06-18-2007.
  9 +
  10 +Changes
  11 +======
  12 +Please refer to the CHANGE_LOG
  13 +
  14 +Installation
  15 +======
  16 +
  17 +For Rails >= 2.1:
  18 +
  19 + script/plugin install git://github.com/mattmatt/acts_as_solr.git
  20 +
  21 +For Rails < 2.1:
  22 +
  23 + cd vendor/plugins
  24 + git clone git://github.com/mattmatt/acts_as_solr.git
  25 + rm -rf acts_as_solr/.git
  26 +
  27 +Make sure you copy `vendor/plugins/acts_as_solr/config/solr.yml` to your Rails
  28 +application's config directory, when you install via `git clone`.
  29 +
  30 +Requirements
  31 +------
  32 +* Java Runtime Environment(JRE) 1.5 aka 5.0 [http://www.java.com/en/download/index.jsp](http://www.java.com/en/download/index.jsp)
  33 +* If you have libxml-ruby installed, make sure it's at least version 0.7
  34 +
  35 +Configuration
  36 +======
  37 +Basically everything is configured to work out of the box. You can use `rake solr:start` and `rake solr:stop`
  38 +to start and stop the Solr web server (an embedded Jetty). If the default JVM options aren't suitable for
  39 +your environment, you can configure them in solr.yml with the option `jvm_options`. There is a default
  40 +set for the production environment to have some more memory available for the JVM than the defaults, but
  41 +feel free to change them to your liking.
  42 +
  43 +Basic Usage
  44 +======
  45 +<pre><code>
  46 +# Just include the line below to any of your ActiveRecord models:
  47 + acts_as_solr
  48 +
  49 +# Or if you want, you can specify only the fields that should be indexed:
  50 + acts_as_solr :fields => [:name, :author]
  51 +
  52 +# Then to find instances of your model, just do:
  53 + Model.find_by_solr(query) #query is a string representing your query
  54 +
  55 +# Please see ActsAsSolr::ActsMethods for a complete info
  56 +
  57 +</code></pre>
  58 +
  59 +
  60 +`acts_as_solr` in your tests
  61 +======
  62 +To test code that uses `acts_as_solr` you must start a Solr server for the test environment. You can do that with `rake solr:start RAILS_ENV=test`
  63 +
  64 +However, if you would like to mock out Solr calls so that a Solr server is not needed (and your tests will run much faster), just add this to your `test_helper.rb` or similar:
  65 +
  66 +<pre><code>
  67 +class ActsAsSolr::Post
  68 + def self.execute(request)
  69 + true
  70 + end
  71 +end
  72 +</pre></code>
  73 +
  74 +([via](http://www.subelsky.com/2007/10/actsassolr-capistranhttpwwwbloggercomim.html#c1646308013209805416))
  75 +
  76 +Authors
  77 +======
  78 +Erik Hatcher: First draft<br>
  79 +Thiago Jackiw: Previous developer<br>
  80 +Luke Francl: Current developer<br>
  81 +Mathias Meyer: Current developer<br>
  82 +
  83 +Support
  84 +=======
  85 +
  86 +Check the [project website](http://mattmatt.github.com/acts_as_solr) or stop by the [Google Group](http://groups.google.com/group/acts_as_solr). Send bug reports through [GitHub Issues](http://github.com/mattmatt/acts_as_solr/issues).
  87 +
  88 +Release Information
  89 +======
  90 +Released under the MIT license.
  91 +
  92 +More info
  93 +======
  94 +The old [acts_as_solr homepage](http://acts-as-solr.railsfreaks.com) is no more. For more up-to-date information, check out the project page of the current mainline on [GitHub](http://github.com/mattmatt/acts_as_solr/wikis).
0 95 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/README.rdoc 0 → 100644
... ... @@ -0,0 +1,84 @@
  1 += acts_as_solr Rails plugin
  2 +
  3 +This plugin adds full text search capabilities and many other nifty features from Apache's Solr[http://lucene.apache.org/solr/] to any Rails model.
  4 +It was based on the first draft by Erik Hatcher.
  5 +
  6 +== Current Release
  7 +
  8 +There is no numbered release yet, but the master branch is always considered stable.
  9 +
  10 +== Support
  11 +
  12 +Check the project website[http://mattmatt.github.com/acts_as_solr] or stop by the Google Group[http://groups.google.com/group/acts_as_solr]. Send bug reports through GitHub[http://github.com/mattmatt/acts_as_solr/issues].
  13 +
  14 +== Changes
  15 +
  16 +Please refer to the CHANGELOG[http://github.com/mattmatt/acts_as_solr/blob/master/CHANGE_LOG]
  17 +
  18 +== Installation
  19 +
  20 +For Rails >= 2.1:
  21 +
  22 + script/plugin install git://github.com/mattmatt/acts_as_solr.git
  23 +
  24 +For Rails < 2.1:
  25 +
  26 + cd vendor/plugins
  27 + git clone git://github.com/mattmatt/acts_as_solr.git
  28 + rm -rf acts_as_solr/.git
  29 +
  30 +Make sure you copy vendor/plugins/acts_as_solr/config/solr.yml to your Rails
  31 +application's config directory, when you install via git clone.
  32 +
  33 +== Requirements
  34 +
  35 +* Java Runtime Environment(JRE) 1.5 aka 5.0 [http://www.java.com/en/download/index.jsp](http://www.java.com/en/download/index.jsp)
  36 +* If you have libxml-ruby installed, make sure it's at least version 0.7
  37 +
  38 +== Configuration
  39 +
  40 +Basically everything is configured to work out of the box. You can use `rake solr:start` and `rake solr:stop`
  41 +to start and stop the Solr web server (an embedded Jetty). If the default JVM options aren't suitable for
  42 +your environment, you can configure them in solr.yml with the option `jvm_options`. There is a default
  43 +set for the production environment to have some more memory available for the JVM than the defaults, but
  44 +feel free to change them to your liking.
  45 +
  46 +== Basic Usage
  47 +
  48 +# Just include the line below to any of your ActiveRecord models:
  49 + acts_as_solr
  50 +
  51 +# Or if you want, you can specify only the fields that should be indexed:
  52 + acts_as_solr :fields => [:name, :author]
  53 +
  54 +# Then to find instances of your model, just do:
  55 + Model.find_by_solr(query) #query is a string representing your query
  56 +
  57 +# Please see ActsAsSolr::ActsMethods for a complete info
  58 +
  59 +== acts_as_solr in your tests
  60 +
  61 +To test code that uses acts_as_solr you must start a Solr server for the test environment. You can do that with
  62 +
  63 + rake solr:start RAILS_ENV=test
  64 +
  65 +However, if you would like to mock out Solr calls so that a Solr server is not needed (and your tests will run much faster), just add this to your `test_helper.rb` or similar:
  66 +
  67 + class ActsAsSolr::Post
  68 + def self.execute(request)
  69 + true
  70 + end
  71 + end
  72 +
  73 +(via[http://www.subelsky.com/2007/10/actsassolr-capistranhttpwwwbloggercomim.html#c1646308013209805416])
  74 +
  75 +== Authors
  76 +
  77 +Erik Hatcher: First draft
  78 +Thiago Jackiw: Previous developer
  79 +Luke Francl: Current developer
  80 +Mathias Meyer: Current developer
  81 +
  82 +== Release Information
  83 +
  84 +Released under the MIT license.
0 85 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/Rakefile 0 → 100644
... ... @@ -0,0 +1,53 @@
  1 +require 'rubygems'
  2 +require 'rake'
  3 +require 'rake/testtask'
  4 +require 'rake/rdoctask'
  5 +
  6 +Dir["#{File.dirname(__FILE__)}/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
  7 +
  8 +desc "Default Task"
  9 +task :default => [:test]
  10 +
  11 +desc "Runs the unit tests"
  12 +task :test => "test:unit"
  13 +
  14 +namespace :test do
  15 + task :setup do
  16 + ENV['RAILS_ENV'] = "test"
  17 + require File.dirname(__FILE__) + '/config/solr_environment'
  18 + puts "Using " + DB
  19 + %x(mysql -u#{MYSQL_USER} < #{File.dirname(__FILE__) + "/test/fixtures/db_definitions/mysql.sql"}) if DB == 'mysql'
  20 +
  21 + Rake::Task["test:migrate"].invoke
  22 + end
  23 +
  24 + desc 'Measures test coverage using rcov'
  25 + task :rcov => :setup do
  26 + rm_f "coverage"
  27 + rm_f "coverage.data"
  28 + rcov = "rcov --rails --aggregate coverage.data --text-summary -Ilib"
  29 +
  30 + system("#{rcov} --html #{Dir.glob('test/**/*_test.rb').join(' ')}")
  31 + system("open coverage/index.html") if PLATFORM['darwin']
  32 + end
  33 +
  34 + desc 'Runs the functional tests, testing integration with Solr'
  35 + Rake::TestTask.new('functional' => :setup) do |t|
  36 + t.pattern = "test/functional/*_test.rb"
  37 + t.verbose = true
  38 + end
  39 +
  40 + desc "Unit tests"
  41 + Rake::TestTask.new(:unit) do |t|
  42 + t.libs << 'test/unit'
  43 + t.pattern = "test/unit/*_shoulda.rb"
  44 + t.verbose = true
  45 + end
  46 +end
  47 +
  48 +Rake::RDocTask.new do |rd|
  49 + rd.main = "README.rdoc"
  50 + rd.rdoc_dir = "rdoc"
  51 + rd.rdoc_files.exclude("lib/solr/**/*.rb", "lib/solr.rb")
  52 + rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
  53 +end
0 54 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/TESTING_THE_PLUGIN 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +acts_as_solr comes with a quick and fast unit test suite, and with a longer-running
  2 +functional test suite, the latter testing the actual integration with Solr.
  3 +
  4 +The unit test suite is written using Shoulda, so make sure you have a recent version
  5 +installed.
  6 +
  7 +Running `rake test` or just `rake` will run both test suites. Use `rake test:unit` to
  8 +just run the unit test suite.
  9 +
  10 +== How to run functional tests for this plugin:
  11 +To run the acts_as_solr's plugin tests run the following steps:
  12 +
  13 +- create a MySQL database called "actsassolr_test" (if you want to use MySQL)
  14 +
  15 +- create a new Rails project, if needed (the plugin can only be tested from within a Rails project); move/checkout acts_as_solr into its vendor/plugins/, as usual
  16 +
  17 +- copy vendor/plugins/acts_as_solr/config/solr.yml to config/ (the Rails config folder)
  18 +
  19 +- rake solr:start RAILS_ENV=test
  20 +
  21 +- rake test:functional (Accepts the following arguments: DB=sqlite|mysql and MYSQL_USER=user)
  22 +
  23 +== Troubleshooting:
  24 +If for some reason the tests don't run and you get MySQL errors, make sure you edit the MYSQL_USER entry under
  25 +config/environment.rb. It's recommended to create or use a MySQL user with no password.
... ...
vendor/plugins/acts_as_solr/config/solr.yml 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +# Config file for the acts_as_solr plugin.
  2 +#
  3 +# If you change the host or port number here, make sure you update
  4 +# them in your Solr config file
  5 +
  6 +development:
  7 + url: http://127.0.0.1:8982/solr
  8 +
  9 +production:
  10 + url: http://127.0.0.1:8983/solr
  11 + jvm_options: -server -d64 -Xmx1024M -Xms64M
  12 +
  13 +test:
  14 + url: http://127.0.0.1:8981/solr
  15 +
... ...
vendor/plugins/acts_as_solr/config/solr_environment.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +ENV['RAILS_ENV'] = (ENV['RAILS_ENV'] || 'development').dup
  2 +# RAILS_ROOT isn't defined yet, so figure it out.
  3 +rails_root_dir = "#{File.dirname(File.expand_path(__FILE__))}/../../../../"
  4 +SOLR_PATH = "#{File.dirname(File.expand_path(__FILE__))}/../solr" unless defined? SOLR_PATH
  5 +
  6 +SOLR_LOGS_PATH = "#{rails_root_dir}/log" unless defined? SOLR_LOGS_PATH
  7 +SOLR_PIDS_PATH = "#{rails_root_dir}/tmp/pids" unless defined? SOLR_PIDS_PATH
  8 +SOLR_DATA_PATH = "#{rails_root_dir}/solr/#{ENV['RAILS_ENV']}" unless defined? SOLR_DATA_PATH
  9 +
  10 +unless defined? SOLR_PORT
  11 + config = YAML::load_file(rails_root_dir+'/config/solr.yml')
  12 +
  13 + SOLR_PORT = ENV['PORT'] || URI.parse(config[ENV['RAILS_ENV']]['url']).port
  14 +end
  15 +
  16 +SOLR_JVM_OPTIONS = config[ENV['RAILS_ENV']]['jvm_options'] unless defined? SOLR_JVM_OPTIONS
  17 +
  18 +if ENV['RAILS_ENV'] == 'test'
  19 + DB = (ENV['DB'] ? ENV['DB'] : 'mysql') unless defined?(DB)
  20 + MYSQL_USER = (ENV['MYSQL_USER'].nil? ? 'root' : ENV['MYSQL_USER']) unless defined? MYSQL_USER
  21 + require File.join(File.dirname(File.expand_path(__FILE__)), '..', 'test', 'db', 'connections', DB, 'connection.rb')
  22 +end
... ...
vendor/plugins/acts_as_solr/init.rb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +# Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
  2 +#
  3 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +# of this software and associated documentation files (the "Software"), to deal
  5 +# in the Software without restriction, including without limitation the rights
  6 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +# copies of the Software, and to permit persons to whom the Software is
  8 +# furnished to do so, subject to the following conditions:
  9 +#
  10 +# The above copyright notice and this permission notice shall be included in all
  11 +# copies or substantial portions of the Software.
  12 +#
  13 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +# SOFTWARE.
  20 +
  21 +require 'acts_as_solr'
0 22 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/install.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +require 'fileutils'
  2 +
  3 +def install(file)
  4 + puts "Installing: #{file}"
  5 + target = File.join(File.dirname(__FILE__), '..', '..', '..', file)
  6 + FileUtils.cp File.join(File.dirname(__FILE__), file), target
  7 + dir_to_rename = File.dirname(__FILE__) + '/../trunk'
  8 + FileUtils.mv(dir_to_rename, File.dirname(__FILE__) + '/../acts_as_solr') if File.exists? dir_to_rename
  9 +end
  10 +
  11 +install File.join( 'config', 'solr.yml' )
... ...
vendor/plugins/acts_as_solr/lib/acts_as_solr.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +# Copyright (c) 2006 Erik Hatcher, Thiago Jackiw
  2 +#
  3 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +# of this software and associated documentation files (the "Software"), to deal
  5 +# in the Software without restriction, including without limitation the rights
  6 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +# copies of the Software, and to permit persons to whom the Software is
  8 +# furnished to do so, subject to the following conditions:
  9 +#
  10 +# The above copyright notice and this permission notice shall be included in all
  11 +# copies or substantial portions of the Software.
  12 +#
  13 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +# SOFTWARE.
  20 +
  21 +require 'active_record'
  22 +require 'rexml/document'
  23 +require 'net/http'
  24 +require 'yaml'
  25 +
  26 +require File.dirname(__FILE__) + '/solr'
  27 +require File.dirname(__FILE__) + '/acts_methods'
  28 +require File.dirname(__FILE__) + '/class_methods'
  29 +require File.dirname(__FILE__) + '/instance_methods'
  30 +require File.dirname(__FILE__) + '/common_methods'
  31 +require File.dirname(__FILE__) + '/deprecation'
  32 +require File.dirname(__FILE__) + '/search_results'
  33 +require File.dirname(__FILE__) + '/lazy_document'
  34 +module ActsAsSolr
  35 +
  36 + class Post
  37 + def self.execute(request)
  38 + begin
  39 + if File.exists?(RAILS_ROOT+'/config/solr.yml')
  40 + config = YAML::load_file(RAILS_ROOT+'/config/solr.yml')
  41 + url = config[RAILS_ENV]['url']
  42 + # for backwards compatibility
  43 + url ||= "http://#{config[RAILS_ENV]['host']}:#{config[RAILS_ENV]['port']}/#{config[RAILS_ENV]['servlet_path']}"
  44 + else
  45 + url = 'http://localhost:8982/solr'
  46 + end
  47 + connection = Solr::Connection.new(url)
  48 + return connection.send(request)
  49 + rescue
  50 + raise "Couldn't connect to the Solr server at #{url}. #{$!}"
  51 + false
  52 + end
  53 + end
  54 + end
  55 +
  56 +end
  57 +
  58 +# reopen ActiveRecord and include the acts_as_solr method
  59 +ActiveRecord::Base.extend ActsAsSolr::ActsMethods
... ...
vendor/plugins/acts_as_solr/lib/acts_methods.rb 0 → 100644
... ... @@ -0,0 +1,279 @@
  1 +module ActsAsSolr #:nodoc:
  2 +
  3 + module ActsMethods
  4 +
  5 + # declares a class as solr-searchable
  6 + #
  7 + # ==== options:
  8 + # fields:: This option can be used to specify only the fields you'd
  9 + # like to index. If not given, all the attributes from the
  10 + # class will be indexed. You can also use this option to
  11 + # include methods that should be indexed as fields
  12 + #
  13 + # class Movie < ActiveRecord::Base
  14 + # acts_as_solr :fields => [:name, :description, :current_time]
  15 + # def current_time
  16 + # Time.now.to_s
  17 + # end
  18 + # end
  19 + #
  20 + # Each field passed can also be a hash with the value being a field type
  21 + #
  22 + # class Electronic < ActiveRecord::Base
  23 + # acts_as_solr :fields => [{:price => :range_float}]
  24 + # def current_time
  25 + # Time.now
  26 + # end
  27 + # end
  28 + #
  29 + # The field types accepted are:
  30 + #
  31 + # :float:: Index the field value as a float (ie.: 12.87)
  32 + # :integer:: Index the field value as an integer (ie.: 31)
  33 + # :boolean:: Index the field value as a boolean (ie.: true/false)
  34 + # :date:: Index the field value as a date (ie.: Wed Nov 15 23:13:03 PST 2006)
  35 + # :string:: Index the field value as a text string, not applying the same indexing
  36 + # filters as a regular text field
  37 + # :range_integer:: Index the field value for integer range queries (ie.:[5 TO 20])
  38 + # :range_float:: Index the field value for float range queries (ie.:[14.56 TO 19.99])
  39 + #
  40 + # Setting the field type preserves its original type when indexed
  41 + #
  42 + # The field may also be passed with a hash value containing options
  43 + #
  44 + # class Author < ActiveRecord::Base
  45 + # acts_as_solr :fields => [{:full_name => {:type => :text, :as => :name}}]
  46 + # def full_name
  47 + # self.first_name + ' ' + self.last_name
  48 + # end
  49 + # end
  50 + #
  51 + # The options accepted are:
  52 + #
  53 + # :type:: Index the field using the specified type
  54 + # :as:: Index the field using the specified field name
  55 + #
  56 + # additional_fields:: This option takes fields to be include in the index
  57 + # in addition to those derived from the database. You
  58 + # can also use this option to include custom fields
  59 + # derived from methods you define. This option will be
  60 + # ignored if the :fields option is given. It also accepts
  61 + # the same field types as the option above
  62 + #
  63 + # class Movie < ActiveRecord::Base
  64 + # acts_as_solr :additional_fields => [:current_time]
  65 + # def current_time
  66 + # Time.now.to_s
  67 + # end
  68 + # end
  69 + #
  70 + # exclude_fields:: This option taks an array of fields that should be ignored from indexing:
  71 + #
  72 + # class User < ActiveRecord::Base
  73 + # acts_as_solr :exclude_fields => [:password, :login, :credit_card_number]
  74 + # end
  75 + #
  76 + # include:: This option can be used for association indexing, which
  77 + # means you can include any :has_one, :has_many, :belongs_to
  78 + # and :has_and_belongs_to_many association to be indexed:
  79 + #
  80 + # class Category < ActiveRecord::Base
  81 + # has_many :books
  82 + # acts_as_solr :include => [:books]
  83 + # end
  84 + #
  85 + # Each association may also be specified as a hash with an option hash as a value
  86 + #
  87 + # class Book < ActiveRecord::Base
  88 + # belongs_to :author
  89 + # has_many :distribution_companies
  90 + # has_many :copyright_dates
  91 + # has_many :media_types
  92 + # acts_as_solr(
  93 + # :fields => [:name, :description],
  94 + # :include => [
  95 + # {:author => {:using => :fullname, :as => :name}},
  96 + # {:media_types => {:using => lambda{|media| type_lookup(media.id)}}}
  97 + # {:distribution_companies => {:as => :distributor, :multivalued => true}},
  98 + # {:copyright_dates => {:as => :copyright, :type => :date}}
  99 + # ]
  100 + # ]
  101 + #
  102 + # The options accepted are:
  103 + #
  104 + # :type:: Index the associated objects using the specified type
  105 + # :as:: Index the associated objects using the specified field name
  106 + # :using:: Index the associated objects using the value returned by the specified method or proc. If a method
  107 + # symbol is supplied, it will be sent to each object to look up the value to index; if a proc is
  108 + # supplied, it will be called once for each object with the object as the only argument
  109 + # :multivalued:: Index the associated objects using one field for each object rather than joining them
  110 + # all into a single field
  111 + #
  112 + # facets:: This option can be used to specify the fields you'd like to
  113 + # index as facet fields
  114 + #
  115 + # class Electronic < ActiveRecord::Base
  116 + # acts_as_solr :facets => [:category, :manufacturer]
  117 + # end
  118 + #
  119 + # boost:: You can pass a boost (float) value that will be used to boost the document and/or a field. To specify a more
  120 + # boost for the document, you can either pass a block or a symbol. The block will be called with the record
  121 + # as an argument, a symbol will result in the according method being called:
  122 + #
  123 + # class Electronic < ActiveRecord::Base
  124 + # acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => 10.0
  125 + # end
  126 + #
  127 + # class Electronic < ActiveRecord::Base
  128 + # acts_as_solr :fields => [{:price => {:boost => 5.0}}], :boost => proc {|record| record.id + 120*37}
  129 + # end
  130 + #
  131 + # class Electronic < ActiveRecord::Base
  132 + # acts_as_solr :fields => [{:price => {:boost => :price_rating}}], :boost => 10.0
  133 + # end
  134 + #
  135 + # if:: Only indexes the record if the condition evaluated is true. The argument has to be
  136 + # either a symbol, string (to be eval'ed), proc/method, or class implementing a static
  137 + # validation method. It behaves the same way as ActiveRecord's :if option.
  138 + #
  139 + # class Electronic < ActiveRecord::Base
  140 + # acts_as_solr :if => proc{|record| record.is_active?}
  141 + # end
  142 + #
  143 + # offline:: Assumes that your using an outside mechanism to explicitly trigger indexing records, e.g. you only
  144 + # want to update your index through some asynchronous mechanism. Will accept either a boolean or a block
  145 + # that will be evaluated before actually contacting the index for saving or destroying a document. Defaults
  146 + # to false. It doesn't refer to the mechanism of an offline index in general, but just to get a centralized point
  147 + # where you can control indexing. Note: This is only enabled for saving records. acts_as_solr doesn't always like
  148 + # it, if you have a different number of results coming from the database and the index. This might be rectified in
  149 + # another patch to support lazy loading.
  150 + #
  151 + # class Electronic < ActiveRecord::Base
  152 + # acts_as_solr :offline => proc {|record| record.automatic_indexing_disabled?}
  153 + # end
  154 + #
  155 + # auto_commit:: The commit command will be sent to Solr only if its value is set to true:
  156 + #
  157 + # class Author < ActiveRecord::Base
  158 + # acts_as_solr :auto_commit => false
  159 + # end
  160 + #
  161 + def acts_as_solr(options={}, solr_options={})
  162 +
  163 + extend ClassMethods
  164 + include InstanceMethods
  165 + include CommonMethods
  166 + include ParserMethods
  167 +
  168 + cattr_accessor :configuration
  169 + cattr_accessor :solr_configuration
  170 +
  171 + self.configuration = {
  172 + :fields => nil,
  173 + :additional_fields => nil,
  174 + :exclude_fields => [],
  175 + :auto_commit => true,
  176 + :include => nil,
  177 + :facets => nil,
  178 + :boost => nil,
  179 + :if => "true",
  180 + :offline => false
  181 + }
  182 + self.solr_configuration = {
  183 + :type_field => "type_s",
  184 + :primary_key_field => "pk_i",
  185 + :default_boost => 1.0
  186 + }
  187 +
  188 + configuration.update(options) if options.is_a?(Hash)
  189 + solr_configuration.update(solr_options) if solr_options.is_a?(Hash)
  190 + Deprecation.validate_index(configuration)
  191 +
  192 + configuration[:solr_fields] = {}
  193 + configuration[:solr_includes] = {}
  194 +
  195 + after_save :solr_save
  196 + after_destroy :solr_destroy
  197 +
  198 + if configuration[:fields].respond_to?(:each)
  199 + process_fields(configuration[:fields])
  200 + else
  201 + process_fields(self.new.attributes.keys.map { |k| k.to_sym })
  202 + process_fields(configuration[:additional_fields])
  203 + end
  204 +
  205 + if configuration[:include].respond_to?(:each)
  206 + process_includes(configuration[:include])
  207 + end
  208 + end
  209 +
  210 + private
  211 + def get_field_value(field)
  212 + field_name, options = determine_field_name_and_options(field)
  213 + configuration[:solr_fields][field_name] = options
  214 +
  215 + define_method("#{field_name}_for_solr".to_sym) do
  216 + begin
  217 + value = self[field_name] || self.instance_variable_get("@#{field_name.to_s}".to_sym) || self.send(field_name.to_sym)
  218 + case options[:type]
  219 + # format dates properly; return nil for nil dates
  220 + when :date
  221 + value ? (value.respond_to?(:utc) ? value.utc : value).strftime("%Y-%m-%dT%H:%M:%SZ") : nil
  222 + else value
  223 + end
  224 + rescue
  225 + puts $!
  226 + logger.debug "There was a problem getting the value for the field '#{field_name}': #{$!}"
  227 + value = ''
  228 + end
  229 + end
  230 + end
  231 +
  232 + def process_fields(raw_field)
  233 + if raw_field.respond_to?(:each)
  234 + raw_field.each do |field|
  235 + next if configuration[:exclude_fields].include?(field)
  236 + get_field_value(field)
  237 + end
  238 + end
  239 + end
  240 +
  241 + def process_includes(includes)
  242 + if includes.respond_to?(:each)
  243 + includes.each do |assoc|
  244 + field_name, options = determine_field_name_and_options(assoc)
  245 + configuration[:solr_includes][field_name] = options
  246 + end
  247 + end
  248 + end
  249 +
  250 + def determine_field_name_and_options(field)
  251 + if field.is_a?(Hash)
  252 + name = field.keys.first
  253 + options = field.values.first
  254 + if options.is_a?(Hash)
  255 + [name, {:type => type_for_field(field)}.merge(options)]
  256 + else
  257 + [name, {:type => options}]
  258 + end
  259 + else
  260 + [field, {:type => type_for_field(field)}]
  261 + end
  262 + end
  263 +
  264 + def type_for_field(field)
  265 + if configuration[:facets] && configuration[:facets].include?(field)
  266 + :facet
  267 + elsif column = columns_hash[field.to_s]
  268 + case column.type
  269 + when :string then :text
  270 + when :datetime then :date
  271 + when :time then :date
  272 + else column.type
  273 + end
  274 + else
  275 + :text
  276 + end
  277 + end
  278 + end
  279 +end
0 280 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/class_methods.rb 0 → 100644
... ... @@ -0,0 +1,239 @@
  1 +require File.dirname(__FILE__) + '/common_methods'
  2 +require File.dirname(__FILE__) + '/parser_methods'
  3 +
  4 +module ActsAsSolr #:nodoc:
  5 +
  6 + module ClassMethods
  7 + include CommonMethods
  8 + include ParserMethods
  9 +
  10 + # Finds instances of a model. Terms are ANDed by default, can be overwritten
  11 + # by using OR between terms
  12 + #
  13 + # Here's a sample (untested) code for your controller:
  14 + #
  15 + # def search
  16 + # results = Book.find_by_solr params[:query]
  17 + # end
  18 + #
  19 + # You can also search for specific fields by searching for 'field:value'
  20 + #
  21 + # ====options:
  22 + # offset:: - The first document to be retrieved (offset)
  23 + # limit:: - The number of rows per page
  24 + # order:: - Orders (sort by) the result set using a given criteria:
  25 + #
  26 + # Book.find_by_solr 'ruby', :order => 'description asc'
  27 + #
  28 + # field_types:: This option is deprecated and will be obsolete by version 1.0.
  29 + # There's no need to specify the :field_types anymore when doing a
  30 + # search in a model that specifies a field type for a field. The field
  31 + # types are automatically traced back when they're included.
  32 + #
  33 + # class Electronic < ActiveRecord::Base
  34 + # acts_as_solr :fields => [{:price => :range_float}]
  35 + # end
  36 + #
  37 + # facets:: This option argument accepts the following arguments:
  38 + # fields:: The fields to be included in the faceted search (Solr's facet.field)
  39 + # query:: The queries to be included in the faceted search (Solr's facet.query)
  40 + # zeros:: Display facets with count of zero. (true|false)
  41 + # sort:: Sorts the faceted resuls by highest to lowest count. (true|false)
  42 + # browse:: This is where the 'drill-down' of the facets work. Accepts an array of
  43 + # fields in the format "facet_field:term"
  44 + # mincount:: Replacement for zeros (it has been deprecated in Solr). Specifies the
  45 + # minimum count necessary for a facet field to be returned. (Solr's
  46 + # facet.mincount) Overrides :zeros if it is specified. Default is 0.
  47 + #
  48 + # dates:: Run date faceted queries using the following arguments:
  49 + # fields:: The fields to be included in the faceted date search (Solr's facet.date).
  50 + # It may be either a String/Symbol or Hash. If it's a hash the options are the
  51 + # same as date_facets minus the fields option (i.e., :start:, :end, :gap, :other,
  52 + # :between). These options if provided will override the base options.
  53 + # (Solr's f.<field_name>.date.<key>=<value>).
  54 + # start:: The lower bound for the first date range for all Date Faceting. Required if
  55 + # :fields is present
  56 + # end:: The upper bound for the last date range for all Date Faceting. Required if
  57 + # :fields is prsent
  58 + # gap:: The size of each date range expressed as an interval to be added to the lower
  59 + # bound using the DateMathParser syntax. Required if :fields is prsent
  60 + # hardend:: A Boolean parameter instructing Solr what do do in the event that
  61 + # facet.date.gap does not divide evenly between facet.date.start and facet.date.end.
  62 + # other:: This param indicates that in addition to the counts for each date range
  63 + # constraint between facet.date.start and facet.date.end, other counds should be
  64 + # calculated. May specify more then one in an Array. The possible options are:
  65 + # before:: - all records with lower bound less than start
  66 + # after:: - all records with upper bound greater than end
  67 + # between:: - all records with field values between start and end
  68 + # none:: - compute no other bounds (useful in per field assignment)
  69 + # all:: - shortcut for before, after, and between
  70 + # filter:: Similar to :query option provided by :facets, in that accepts an array of
  71 + # of date queries to limit results. Can not be used as a part of a :field hash.
  72 + # This is the only option that can be used if :fields is not present.
  73 + #
  74 + # Example:
  75 + #
  76 + # Electronic.find_by_solr "memory", :facets => {:zeros => false, :sort => true,
  77 + # :query => ["price:[* TO 200]",
  78 + # "price:[200 TO 500]",
  79 + # "price:[500 TO *]"],
  80 + # :fields => [:category, :manufacturer],
  81 + # :browse => ["category:Memory","manufacturer:Someone"]}
  82 + #
  83 + #
  84 + # Examples of date faceting:
  85 + #
  86 + # basic:
  87 + # Electronic.find_by_solr "memory", :facets => {:dates => {:fields => [:updated_at, :created_at],
  88 + # :start => 'NOW-10YEARS/DAY', :end => 'NOW/DAY', :gap => '+2YEARS', :other => :before}}
  89 + #
  90 + # advanced:
  91 + # Electronic.find_by_solr "memory", :facets => {:dates => {:fields => [:updated_at,
  92 + # {:created_at => {:start => 'NOW-20YEARS/DAY', :end => 'NOW-10YEARS/DAY', :other => [:before, :after]}
  93 + # }], :start => 'NOW-10YEARS/DAY', :end => 'NOW/DAY', :other => :before, :filter =>
  94 + # ["created_at:[NOW-10YEARS/DAY TO NOW/DAY]", "updated_at:[NOW-1YEAR/DAY TO NOW/DAY]"]}}
  95 + #
  96 + # filter only:
  97 + # Electronic.find_by_solr "memory", :facets => {:dates => {:filter => "updated_at:[NOW-1YEAR/DAY TO NOW/DAY]"}}
  98 + #
  99 + #
  100 + #
  101 + # scores:: If set to true this will return the score as a 'solr_score' attribute
  102 + # for each one of the instances found. Does not currently work with find_id_by_solr
  103 + #
  104 + # books = Book.find_by_solr 'ruby OR splinter', :scores => true
  105 + # books.records.first.solr_score
  106 + # => 1.21321397
  107 + # books.records.last.solr_score
  108 + # => 0.12321548
  109 + #
  110 + # lazy:: If set to true the search will return objects that will touch the database when you ask for one
  111 + # of their attributes for the first time. Useful when you're using fragment caching based solely on
  112 + # types and ids.
  113 + #
  114 + def find_by_solr(query, options={})
  115 + data = parse_query(query, options)
  116 + return parse_results(data, options) if data
  117 + end
  118 +
  119 + # Finds instances of a model and returns an array with the ids:
  120 + # Book.find_id_by_solr "rails" => [1,4,7]
  121 + # The options accepted are the same as find_by_solr
  122 + #
  123 + def find_id_by_solr(query, options={})
  124 + data = parse_query(query, options)
  125 + return parse_results(data, {:format => :ids}) if data
  126 + end
  127 +
  128 + # This method can be used to execute a search across multiple models:
  129 + # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie]
  130 + #
  131 + # ====options:
  132 + # Accepts the same options as find_by_solr plus:
  133 + # models:: The additional models you'd like to include in the search
  134 + # results_format:: Specify the format of the results found
  135 + # :objects :: Will return an array with the results being objects (default). Example:
  136 + # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :objects
  137 + # :ids :: Will return an array with the ids of each entry found. Example:
  138 + # Book.multi_solr_search "Napoleon OR Tom", :models => [Movie], :results_format => :ids
  139 + # => [{"id" => "Movie:1"},{"id" => Book:1}]
  140 + # Where the value of each array is as Model:instance_id
  141 + # scores:: If set to true this will return the score as a 'solr_score' attribute
  142 + # for each one of the instances found. Does not currently work with find_id_by_solr
  143 + #
  144 + # books = Book.multi_solr_search 'ruby OR splinter', :scores => true
  145 + # books.records.first.solr_score
  146 + # => 1.21321397
  147 + # books.records.last.solr_score
  148 + # => 0.12321548
  149 + #
  150 + def multi_solr_search(query, options = {})
  151 + models = multi_model_suffix(options)
  152 + options.update(:results_format => :objects) unless options[:results_format]
  153 + data = parse_query(query, options, models)
  154 +
  155 + if data.nil? or data.total_hits == 0
  156 + return SearchResults.new(:docs => [], :total => 0)
  157 + end
  158 +
  159 + result = find_multi_search_objects(data, options)
  160 + if options[:scores] and options[:results_format] == :objects
  161 + add_scores(result, data)
  162 + end
  163 + SearchResults.new :docs => result, :total => data.total_hits
  164 + end
  165 +
  166 + def find_multi_search_objects(data, options)
  167 + result = []
  168 + if options[:results_format] == :objects
  169 + data.hits.each do |doc|
  170 + k = doc.fetch('id').first.to_s.split(':')
  171 + result << k[0].constantize.find_by_id(k[1])
  172 + end
  173 + elsif options[:results_format] == :ids
  174 + data.hits.each{|doc| result << {"id" => doc.values.pop.to_s}}
  175 + end
  176 + result
  177 + end
  178 +
  179 + def multi_model_suffix(options)
  180 + models = "AND (#{solr_configuration[:type_field]}:#{self.name}"
  181 + models << " OR " + options[:models].collect {|m| "#{solr_configuration[:type_field]}:" + m.to_s}.join(" OR ") if options[:models].is_a?(Array)
  182 + models << ")"
  183 + end
  184 +
  185 + # returns the total number of documents found in the query specified:
  186 + # Book.count_by_solr 'rails' => 3
  187 + #
  188 + def count_by_solr(query, options = {})
  189 + data = parse_query(query, options)
  190 + data.total_hits
  191 + end
  192 +
  193 + # It's used to rebuild the Solr index for a specific model.
  194 + # Book.rebuild_solr_index
  195 + #
  196 + # If batch_size is greater than 0, adds will be done in batches.
  197 + # NOTE: If using sqlserver, be sure to use a finder with an explicit order.
  198 + # Non-edge versions of rails do not handle pagination correctly for sqlserver
  199 + # without an order clause.
  200 + #
  201 + # If a finder block is given, it will be called to retrieve the items to index.
  202 + # This can be very useful for things such as updating based on conditions or
  203 + # using eager loading for indexed associations.
  204 + def rebuild_solr_index(batch_size=100, &finder)
  205 + finder ||= lambda { |ar, options| ar.find(:all, options.merge({:order => self.primary_key})) }
  206 + start_time = Time.now
  207 +
  208 + if batch_size > 0
  209 + items_processed = 0
  210 + limit = batch_size
  211 + offset = 0
  212 + begin
  213 + iteration_start = Time.now
  214 + items = finder.call(self, {:limit => limit, :offset => offset})
  215 + add_batch = items.collect { |content| content.to_solr_doc }
  216 +
  217 + if items.size > 0
  218 + solr_add add_batch
  219 + solr_commit
  220 + end
  221 +
  222 + items_processed += items.size
  223 + last_id = items.last.id if items.last
  224 + time_so_far = Time.now - start_time
  225 + iteration_time = Time.now - iteration_start
  226 + 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}"
  227 + offset += items.size
  228 + end while items.nil? || items.size > 0
  229 + else
  230 + items = finder.call(self, {})
  231 + items.each { |content| content.solr_save }
  232 + items_processed = items.size
  233 + end
  234 + solr_optimize
  235 + logger.info items_processed > 0 ? "Index for #{self.name} has been rebuilt" : "Nothing to index for #{self.name}"
  236 + end
  237 + end
  238 +
  239 +end
... ...
vendor/plugins/acts_as_solr/lib/common_methods.rb 0 → 100644
... ... @@ -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 90 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/deprecation.rb 0 → 100644
... ... @@ -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
... ...
vendor/plugins/acts_as_solr/lib/instance_methods.rb 0 → 100644
... ... @@ -0,0 +1,166 @@
  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 +
  165 + end
  166 +end
0 167 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/lazy_document.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +module ActsAsSolr
  2 + class LazyDocument
  3 + attr_reader :id, :clazz
  4 +
  5 + def initialize(id, clazz)
  6 + @id = id
  7 + @clazz = clazz
  8 + end
  9 +
  10 + def method_missing(name, *args)
  11 + unless @__instance
  12 + @__instance = @clazz.find(@id)
  13 + end
  14 +
  15 + @__instance.send(name, *args)
  16 + end
  17 + end
  18 +end
0 19 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/parser_methods.rb 0 → 100644
... ... @@ -0,0 +1,201 @@
  1 +module ActsAsSolr #:nodoc:
  2 + module ParserMethods
  3 + protected
  4 +
  5 + # Method used by mostly all the ClassMethods when doing a search
  6 + def parse_query(query=nil, options={}, models=nil)
  7 + valid_options = [:offset, :limit, :facets, :models, :results_format, :order, :scores, :operator, :include, :lazy]
  8 + query_options = {}
  9 +
  10 + return nil if (query.nil? || query.strip == '')
  11 +
  12 + raise "Invalid parameters: #{(options.keys - valid_options).join(',')}" unless (options.keys - valid_options).empty?
  13 + begin
  14 + Deprecation.validate_query(options)
  15 + query_options[:start] = options[:offset]
  16 + query_options[:rows] = options[:limit]
  17 + query_options[:operator] = options[:operator]
  18 +
  19 + # first steps on the facet parameter processing
  20 + if options[:facets]
  21 + query_options[:facets] = {}
  22 + query_options[:facets][:limit] = -1 # TODO: make this configurable
  23 + query_options[:facets][:sort] = :count if options[:facets][:sort]
  24 + query_options[:facets][:mincount] = 0
  25 + query_options[:facets][:mincount] = 1 if options[:facets][:zeros] == false
  26 + # override the :zeros (it's deprecated anyway) if :mincount exists
  27 + query_options[:facets][:mincount] = options[:facets][:mincount] if options[:facets][:mincount]
  28 + query_options[:facets][:fields] = options[:facets][:fields].collect{|k| "#{k}_facet"} if options[:facets][:fields]
  29 + query_options[:filter_queries] = replace_types([*options[:facets][:browse]].collect{|k| "#{k.sub!(/ *: */,"_facet:")}"}) if options[:facets][:browse]
  30 + query_options[:facets][:queries] = replace_types(options[:facets][:query].collect{|k| "#{k.sub!(/ *: */,"_t:")}"}) if options[:facets][:query]
  31 +
  32 +
  33 + if options[:facets][:dates]
  34 + query_options[:date_facets] = {}
  35 + # if options[:facets][:dates][:fields] exists then :start, :end, and :gap must be there
  36 + if options[:facets][:dates][:fields]
  37 + [:start, :end, :gap].each { |k| raise "#{k} must be present in faceted date query" unless options[:facets][:dates].include?(k) }
  38 + query_options[:date_facets][:fields] = []
  39 + options[:facets][:dates][:fields].each { |f|
  40 + if f.kind_of? Hash
  41 + key = f.keys[0]
  42 + query_options[:date_facets][:fields] << {"#{key}_d" => f[key]}
  43 + validate_date_facet_other_options(f[key][:other]) if f[key][:other]
  44 + else
  45 + query_options[:date_facets][:fields] << "#{f}_d"
  46 + end
  47 + }
  48 + end
  49 +
  50 + query_options[:date_facets][:start] = options[:facets][:dates][:start] if options[:facets][:dates][:start]
  51 + query_options[:date_facets][:end] = options[:facets][:dates][:end] if options[:facets][:dates][:end]
  52 + query_options[:date_facets][:gap] = options[:facets][:dates][:gap] if options[:facets][:dates][:gap]
  53 + query_options[:date_facets][:hardend] = options[:facets][:dates][:hardend] if options[:facets][:dates][:hardend]
  54 + query_options[:date_facets][:filter] = replace_types([*options[:facets][:dates][:filter]].collect{|k| "#{k.sub!(/ *:(?!\d) */,"_d:")}"}) if options[:facets][:dates][:filter]
  55 +
  56 + if options[:facets][:dates][:other]
  57 + validate_date_facet_other_options(options[:facets][:dates][:other])
  58 + query_options[:date_facets][:other] = options[:facets][:dates][:other]
  59 + end
  60 +
  61 + end
  62 + end
  63 +
  64 + if models.nil?
  65 + # TODO: use a filter query for type, allowing Solr to cache it individually
  66 + models = "AND #{solr_type_condition}"
  67 + field_list = solr_configuration[:primary_key_field]
  68 + else
  69 + field_list = "id"
  70 + end
  71 +
  72 + query_options[:field_list] = [field_list, 'score']
  73 + query = "(#{query.gsub(/ *: */,"_t:")}) #{models}"
  74 + order = options[:order].split(/\s*,\s*/).collect{|e| e.gsub(/\s+/,'_t ').gsub(/\bscore_t\b/, 'score') }.join(',') if options[:order]
  75 + query_options[:query] = replace_types([query])[0] # TODO adjust replace_types to work with String or Array
  76 +
  77 + if options[:order]
  78 + # TODO: set the sort parameter instead of the old ;order. style.
  79 + query_options[:query] << ';' << replace_types([order], false)[0]
  80 + end
  81 +
  82 + ActsAsSolr::Post.execute(Solr::Request::Standard.new(query_options))
  83 + rescue
  84 + raise "There was a problem executing your search: #{$!} in #{$!.backtrace.first}"
  85 + end
  86 + end
  87 +
  88 + def solr_type_condition
  89 + subclasses.inject("(#{solr_configuration[:type_field]}:#{self.name}") do |condition, subclass|
  90 + condition << " OR #{solr_configuration[:type_field]}:#{subclass.name}"
  91 + end << ')'
  92 + end
  93 +
  94 + # Parses the data returned from Solr
  95 + def parse_results(solr_data, options = {})
  96 + results = {
  97 + :docs => [],
  98 + :total => 0
  99 + }
  100 +
  101 + configuration = {
  102 + :format => :objects
  103 + }
  104 + results.update(:facets => {'facet_fields' => []}) if options[:facets]
  105 + return SearchResults.new(results) if (solr_data.nil? || solr_data.total_hits == 0)
  106 +
  107 + configuration.update(options) if options.is_a?(Hash)
  108 +
  109 + ids = solr_data.hits.collect {|doc| doc["#{solr_configuration[:primary_key_field]}"]}.flatten
  110 +
  111 + result = find_objects(ids, options, configuration)
  112 +
  113 + add_scores(result, solr_data) if configuration[:format] == :objects && options[:scores]
  114 +
  115 + results.update(:facets => solr_data.data['facet_counts']) if options[:facets]
  116 + results.update({:docs => result, :total => solr_data.total_hits, :max_score => solr_data.max_score, :query_time => solr_data.data['responseHeader']['QTime']})
  117 + SearchResults.new(results)
  118 + end
  119 +
  120 +
  121 + def find_objects(ids, options, configuration)
  122 + result = if configuration[:lazy] && configuration[:format] != :ids
  123 + ids.collect {|id| ActsAsSolr::LazyDocument.new(id, self)}
  124 + elsif configuration[:format] == :objects
  125 + conditions = [ "#{self.table_name}.#{primary_key} in (?)", ids ]
  126 + find_options = {:conditions => conditions}
  127 + find_options[:include] = options[:include] if options[:include]
  128 + result = reorder(self.find(:all, find_options), ids)
  129 + else
  130 + ids
  131 + end
  132 +
  133 + result
  134 + end
  135 +
  136 + # Reorders the instances keeping the order returned from Solr
  137 + def reorder(things, ids)
  138 + ordered_things = Array.new(things.size)
  139 + raise "Out of sync! Found #{ids.size} items in index, but only #{things.size} were found in database!" unless things.size == ids.size
  140 + things.each do |thing|
  141 + position = ids.index(thing.id)
  142 + ordered_things[position] = thing
  143 + end
  144 + ordered_things
  145 + end
  146 +
  147 + # Replaces the field types based on the types (if any) specified
  148 + # on the acts_as_solr call
  149 + def replace_types(strings, include_colon=true)
  150 + suffix = include_colon ? ":" : ""
  151 + if configuration[:solr_fields]
  152 + configuration[:solr_fields].each do |name, options|
  153 + solr_name = options[:as] || name.to_s
  154 + solr_type = get_solr_field_type(options[:type])
  155 + field = "#{solr_name}_#{solr_type}#{suffix}"
  156 + strings.each_with_index {|s,i| strings[i] = s.gsub(/#{solr_name.to_s}_t#{suffix}/,field) }
  157 + end
  158 + end
  159 + if configuration[:solr_includes]
  160 + configuration[:solr_includes].each do |association, options|
  161 + solr_name = options[:as] || association.to_s.singularize
  162 + solr_type = get_solr_field_type(options[:type])
  163 + field = "#{solr_name}_#{solr_type}#{suffix}"
  164 + strings.each_with_index {|s,i| strings[i] = s.gsub(/#{solr_name.to_s}_t#{suffix}/,field) }
  165 + end
  166 + end
  167 + strings
  168 + end
  169 +
  170 + # Adds the score to each one of the instances found
  171 + def add_scores(results, solr_data)
  172 + with_score = []
  173 + solr_data.hits.each do |doc|
  174 + with_score.push([doc["score"],
  175 + results.find {|record| scorable_record?(record, doc) }])
  176 + end
  177 + with_score.each do |score, object|
  178 + class << object; attr_accessor :solr_score; end
  179 + object.solr_score = score
  180 + end
  181 + end
  182 +
  183 + def scorable_record?(record, doc)
  184 + doc_id = doc["#{solr_configuration[:primary_key_field]}"]
  185 + if doc_id.nil?
  186 + doc_id = doc["id"]
  187 + "#{record.class.name}:#{record_id(record)}" == doc_id.first.to_s
  188 + else
  189 + record_id(record).to_s == doc_id.to_s
  190 + end
  191 + end
  192 +
  193 + def validate_date_facet_other_options(options)
  194 + valid_other_options = [:after, :all, :before, :between, :none]
  195 + options = [options] unless options.kind_of? Array
  196 + bad_options = options.map {|x| x.to_sym} - valid_other_options
  197 + raise "Invalid option#{'s' if bad_options.size > 1} for faceted date's other param: #{bad_options.join(', ')}. May only be one of :after, :all, :before, :between, :none" if bad_options.size > 0
  198 + end
  199 +
  200 + end
  201 +end
0 202 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/search_results.rb 0 → 100644
... ... @@ -0,0 +1,68 @@
  1 +module ActsAsSolr #:nodoc:
  2 +
  3 + # TODO: Possibly looking into hooking it up with Solr::Response::Standard
  4 + #
  5 + # Class that returns the search results with four methods.
  6 + #
  7 + # books = Book.find_by_solr 'ruby'
  8 + #
  9 + # the above will return a SearchResults class with 4 methods:
  10 + #
  11 + # docs|results|records: will return an array of records found
  12 + #
  13 + # books.records.empty?
  14 + # => false
  15 + #
  16 + # total|num_found|total_hits: will return the total number of records found
  17 + #
  18 + # books.total
  19 + # => 2
  20 + #
  21 + # facets: will return the facets when doing a faceted search
  22 + #
  23 + # max_score|highest_score: returns the highest score found
  24 + #
  25 + # books.max_score
  26 + # => 1.3213213
  27 + #
  28 + #
  29 + class SearchResults
  30 + def initialize(solr_data={})
  31 + @solr_data = solr_data
  32 + end
  33 +
  34 + # Returns an array with the instances. This method
  35 + # is also aliased as docs and records
  36 + def results
  37 + @solr_data[:docs]
  38 + end
  39 +
  40 + # Returns the total records found. This method is
  41 + # also aliased as num_found and total_hits
  42 + def total
  43 + @solr_data[:total]
  44 + end
  45 +
  46 + # Returns the facets when doing a faceted search
  47 + def facets
  48 + @solr_data[:facets]
  49 + end
  50 +
  51 + # Returns the highest score found. This method is
  52 + # also aliased as highest_score
  53 + def max_score
  54 + @solr_data[:max_score]
  55 + end
  56 +
  57 + def query_time
  58 + @solr_data[:query_time]
  59 + end
  60 +
  61 + alias docs results
  62 + alias records results
  63 + alias num_found total
  64 + alias total_hits total
  65 + alias highest_score max_score
  66 + end
  67 +
  68 +end
0 69 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr.rb 0 → 100755
... ... @@ -0,0 +1,21 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +module Solr; end
  14 +require 'solr/exception'
  15 +require 'solr/request'
  16 +require 'solr/connection'
  17 +require 'solr/response'
  18 +require 'solr/util'
  19 +require 'solr/xml'
  20 +require 'solr/importer'
  21 +require 'solr/indexer'
... ...
vendor/plugins/acts_as_solr/lib/solr/connection.rb 0 → 100755
... ... @@ -0,0 +1,179 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'net/http'
  14 +
  15 +# TODO: add a convenience method to POST a Solr .xml file, like Solr's example post.sh
  16 +
  17 +class Solr::Connection
  18 + attr_reader :url, :autocommit, :connection
  19 +
  20 + # create a connection to a solr instance using the url for the solr
  21 + # application context:
  22 + #
  23 + # conn = Solr::Connection.new("http://example.com:8080/solr")
  24 + #
  25 + # if you would prefer to have all adds/updates autocommitted,
  26 + # use :autocommit => :on
  27 + #
  28 + # conn = Solr::Connection.new('http://example.com:8080/solr',
  29 + # :autocommit => :on)
  30 +
  31 + def initialize(url="http://localhost:8983/solr", opts={})
  32 + @url = URI.parse(url)
  33 + unless @url.kind_of? URI::HTTP
  34 + raise "invalid http url: #{url}"
  35 + end
  36 +
  37 + # TODO: Autocommit seems nice at one level, but it currently is confusing because
  38 + # only calls to Connection#add/#update/#delete, though a Connection#send(AddDocument.new(...))
  39 + # does not autocommit. Maybe #send should check for the request types that require a commit and
  40 + # commit in #send instead of the individual methods?
  41 + @autocommit = opts[:autocommit] == :on
  42 +
  43 + # Not actually opening the connection yet, just setting up the persistent connection.
  44 + @connection = Net::HTTP.new(@url.host, @url.port)
  45 +
  46 + @connection.read_timeout = opts[:timeout] if opts[:timeout]
  47 + end
  48 +
  49 + # add a document to the index. you can pass in either a hash
  50 + #
  51 + # conn.add(:id => 123, :title => 'Tlon, Uqbar, Orbis Tertius')
  52 + #
  53 + # or a Solr::Document
  54 + #
  55 + # conn.add(Solr::Document.new(:id => 123, :title = 'On Writing')
  56 + #
  57 + # true/false will be returned to designate success/failure
  58 +
  59 + def add(doc)
  60 + request = Solr::Request::AddDocument.new(doc)
  61 + response = send(request)
  62 + commit if @autocommit
  63 + return response.ok?
  64 + end
  65 +
  66 + # update a document in the index (really just an alias to add)
  67 +
  68 + def update(doc)
  69 + return add(doc)
  70 + end
  71 +
  72 + # performs a standard query and returns a Solr::Response::Standard
  73 + #
  74 + # response = conn.query('borges')
  75 + #
  76 + # alternative you can pass in a block and iterate over hits
  77 + #
  78 + # conn.query('borges') do |hit|
  79 + # puts hit
  80 + # end
  81 + #
  82 + # options include:
  83 + #
  84 + # :sort, :default_field, :rows, :filter_queries, :debug_query,
  85 + # :explain_other, :facets, :highlighting, :mlt,
  86 + # :operator => :or / :and
  87 + # :start => defaults to 0
  88 + # :field_list => array, defaults to ["*", "score"]
  89 +
  90 + def query(query, options={}, &action)
  91 + # TODO: Shouldn't this return an exception if the Solr status is not ok? (rather than true/false).
  92 + create_and_send_query(Solr::Request::Standard, options.update(:query => query), &action)
  93 + end
  94 +
  95 + # performs a dismax search and returns a Solr::Response::Standard
  96 + #
  97 + # response = conn.search('borges')
  98 + #
  99 + # options are same as query, but also include:
  100 + #
  101 + # :tie_breaker, :query_fields, :minimum_match, :phrase_fields,
  102 + # :phrase_slop, :boost_query, :boost_functions
  103 +
  104 + def search(query, options={}, &action)
  105 + create_and_send_query(Solr::Request::Dismax, options.update(:query => query), &action)
  106 + end
  107 +
  108 + # sends a commit message to the server
  109 + def commit(options={})
  110 + response = send(Solr::Request::Commit.new(options))
  111 + return response.ok?
  112 + end
  113 +
  114 + # sends an optimize message to the server
  115 + def optimize
  116 + response = send(Solr::Request::Optimize.new)
  117 + return response.ok?
  118 + end
  119 +
  120 + # pings the connection and returns true/false if it is alive or not
  121 + def ping
  122 + begin
  123 + response = send(Solr::Request::Ping.new)
  124 + return response.ok?
  125 + rescue
  126 + return false
  127 + end
  128 + end
  129 +
  130 + # delete a document from the index using the document id
  131 + def delete(document_id)
  132 + response = send(Solr::Request::Delete.new(:id => document_id))
  133 + commit if @autocommit
  134 + response.ok?
  135 + end
  136 +
  137 + # delete using a query
  138 + def delete_by_query(query)
  139 + response = send(Solr::Request::Delete.new(:query => query))
  140 + commit if @autocommit
  141 + response.ok?
  142 + end
  143 +
  144 + def info
  145 + send(Solr::Request::IndexInfo.new)
  146 + end
  147 +
  148 + # send a given Solr::Request and return a RubyResponse or XmlResponse
  149 + # depending on the type of request
  150 + def send(request)
  151 + data = post(request)
  152 + Solr::Response::Base.make_response(request, data)
  153 + end
  154 +
  155 + # send the http post request to solr; for convenience there are shortcuts
  156 + # to some requests: add(), query(), commit(), delete() or send()
  157 + def post(request)
  158 + response = @connection.post(@url.path + "/" + request.handler,
  159 + request.to_s,
  160 + { "Content-Type" => request.content_type })
  161 +
  162 + case response
  163 + when Net::HTTPSuccess then response.body
  164 + else
  165 + response.error!
  166 + end
  167 +
  168 + end
  169 +
  170 +private
  171 +
  172 + def create_and_send_query(klass, options = {}, &action)
  173 + request = klass.new(options)
  174 + response = send(request)
  175 + return response unless action
  176 + response.each {|hit| action.call(hit)}
  177 + end
  178 +
  179 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/document.rb 0 → 100644
... ... @@ -0,0 +1,78 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +require 'solr/field'
  15 +
  16 +class Solr::Document
  17 + include Enumerable
  18 + attr_accessor :boost
  19 + attr_reader :fields
  20 +
  21 + # Create a new Solr::Document, optionally passing in a hash of
  22 + # key/value pairs for the fields
  23 + #
  24 + # doc = Solr::Document.new(:creator => 'Jorge Luis Borges')
  25 + def initialize(hash={})
  26 + @fields = []
  27 + self << hash
  28 + end
  29 +
  30 + # Append a Solr::Field
  31 + #
  32 + # doc << Solr::Field.new(:creator => 'Jorge Luis Borges')
  33 + #
  34 + # If you are truly lazy you can simply pass in a hash:
  35 + #
  36 + # doc << {:creator => 'Jorge Luis Borges'}
  37 + def <<(fields)
  38 + case fields
  39 + when Hash
  40 + fields.each_pair do |name,value|
  41 + if value.respond_to?(:each) && !value.is_a?(String)
  42 + value.each {|v| @fields << Solr::Field.new(name => v)}
  43 + else
  44 + @fields << Solr::Field.new(name => value)
  45 + end
  46 + end
  47 + when Solr::Field
  48 + @fields << fields
  49 + else
  50 + raise "must pass in Solr::Field or Hash"
  51 + end
  52 + end
  53 +
  54 + # shorthand to allow hash lookups
  55 + # doc['name']
  56 + def [](name)
  57 + field = @fields.find {|f| f.name == name.to_s}
  58 + return field.value if field
  59 + return nil
  60 + end
  61 +
  62 + # shorthand to assign as a hash
  63 + def []=(name,value)
  64 + @fields << Solr::Field.new(name => value)
  65 + end
  66 +
  67 + # convert the Document to a REXML::Element
  68 + def to_xml
  69 + e = Solr::XML::Element.new 'doc'
  70 + e.attributes['boost'] = @boost.to_s if @boost
  71 + @fields.each {|f| e.add_element(f.to_xml)}
  72 + return e
  73 + end
  74 +
  75 + def each(*args, &blk)
  76 + fields.each(&blk)
  77 + end
  78 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/exception.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Exception < Exception; end
... ...
vendor/plugins/acts_as_solr/lib/solr/field.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +require 'time'
  15 +
  16 +class Solr::Field
  17 + VALID_PARAMS = [:boost]
  18 + attr_accessor :name
  19 + attr_accessor :value
  20 + attr_accessor :boost
  21 +
  22 + # Accepts an optional <tt>:boost</tt> parameter, used to boost the relevance of a particular field.
  23 + def initialize(params)
  24 + @boost = params[:boost]
  25 + name_key = (params.keys - VALID_PARAMS).first
  26 + @name, @value = name_key.to_s, params[name_key]
  27 + # Convert any Time values into UTC/XML schema format (which Solr requires).
  28 + @value = @value.respond_to?(:utc) ? @value.utc.xmlschema : @value.to_s
  29 + end
  30 +
  31 + def to_xml
  32 + e = Solr::XML::Element.new 'field'
  33 + e.attributes['name'] = @name
  34 + e.attributes['boost'] = @boost.to_s if @boost
  35 + e.text = @value
  36 + return e
  37 + end
  38 +
  39 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/importer.rb 0 → 100755
... ... @@ -0,0 +1,19 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +module Solr; module Importer; end; end
  14 +require 'solr/importer/mapper'
  15 +require 'solr/importer/array_mapper'
  16 +require 'solr/importer/delimited_file_source'
  17 +require 'solr/importer/hpricot_mapper'
  18 +require 'solr/importer/xpath_mapper'
  19 +require 'solr/importer/solr_source'
0 20 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/array_mapper.rb 0 → 100755
... ... @@ -0,0 +1,26 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +
  14 +
  15 +class Solr::Importer::ArrayMapper < Solr::Importer::Mapper
  16 + # TODO document that initializer takes an array of Mappers [mapper1, mapper2, ... mapperN]
  17 +
  18 + # TODO: make merge conflict handling configurable. as is, the last map fields win.
  19 + def map(orig_data_array)
  20 + mapped_data = {}
  21 + orig_data_array.each_with_index do |data,i|
  22 + mapped_data.merge!(@mapping[i].map(data))
  23 + end
  24 + mapped_data
  25 + end
  26 +end
0 27 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/delimited_file_source.rb 0 → 100755
... ... @@ -0,0 +1,38 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +# For files with the first line containing field names
  14 +# Currently not designed for enormous files, as all lines are
  15 +# read into an array
  16 +class Solr::Importer::DelimitedFileSource
  17 + include Enumerable
  18 +
  19 + def initialize(filename, splitter=/\t/)
  20 + @filename = filename
  21 + @splitter = splitter
  22 + end
  23 +
  24 + def each
  25 + lines = IO.readlines(@filename)
  26 + headers = lines[0].split(@splitter).collect{|h| h.chomp}
  27 +
  28 + lines[1..-1].each do |line|
  29 + data = headers.zip(line.split(@splitter).collect{|s| s.chomp})
  30 + def data.[](key)
  31 + self.assoc(key.to_s)[1]
  32 + end
  33 +
  34 + yield(data)
  35 + end
  36 + end
  37 +
  38 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/hpricot_mapper.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +begin
  14 + require 'hpricot'
  15 +
  16 + class Solr::Importer::HpricotMapper < Solr::Importer::Mapper
  17 + def field_data(doc, path)
  18 + doc.search(path.to_s).collect { |e| e.inner_html }
  19 + end
  20 + end
  21 +rescue LoadError => e # If we can't load hpricot
  22 + class Solr::Importer::HpricotMapper
  23 + def initialize(mapping, options={})
  24 + raise "Hpricot not installed."
  25 + end
  26 + end
  27 +end
0 28 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/mapper.rb 0 → 100755
... ... @@ -0,0 +1,51 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Importer::Mapper
  14 + def initialize(mapping, options={})
  15 + @mapping = mapping
  16 + @options = options
  17 + end
  18 +
  19 + def field_data(orig_data, field_name)
  20 + orig_data[field_name]
  21 + end
  22 +
  23 + def mapped_field_value(orig_data, field_mapping)
  24 + case field_mapping
  25 + when String
  26 + field_mapping
  27 + when Proc
  28 + field_mapping.call(orig_data) # TODO pass in more context, like self or a function for field_data, etc
  29 + when Symbol
  30 + field_data(orig_data, @options[:stringify_symbols] ? field_mapping.to_s : field_mapping)
  31 + when Enumerable
  32 + field_mapping.collect {|orig_field_name| mapped_field_value(orig_data, orig_field_name)}.flatten
  33 + else
  34 + raise "Unknown mapping for #{field_mapping}"
  35 + end
  36 + end
  37 +
  38 + def map(orig_data)
  39 + mapped_data = {}
  40 + @mapping.each do |solr_name, field_mapping|
  41 + value = mapped_field_value(orig_data, field_mapping)
  42 + mapped_data[solr_name] = value if value
  43 + end
  44 +
  45 + mapped_data
  46 + end
  47 +
  48 +
  49 +
  50 +
  51 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/solr_source.rb 0 → 100755
... ... @@ -0,0 +1,43 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr'
  14 +
  15 +class Solr::Importer::SolrSource
  16 + def initialize(solr_url, query, filter_queries=nil, options={})
  17 + @connection = Solr::Connection.new(solr_url)
  18 + @query = query
  19 + @filter_queries = filter_queries
  20 +
  21 + @page_size = options[:page_size] || 1000
  22 + @field_list = options[:field_list] || ["*"]
  23 + end
  24 +
  25 + def each
  26 + done = false
  27 + start = 0
  28 + until done do
  29 + # request N documents from a starting point
  30 + request = Solr::Request::Standard.new(:query => @query,
  31 + :rows => @page_size,
  32 + :start => start,
  33 + :field_list => @field_list,
  34 + :filter_queries => @filter_queries)
  35 + response = @connection.send(request)
  36 + response.each do |doc|
  37 + yield doc # TODO: perhaps convert to HashWithIndifferentAccess.new(doc), so stringify_keys isn't necessary
  38 + end
  39 + done = start + @page_size >= response.total_hits
  40 + start = start + @page_size
  41 + end
  42 + end
  43 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/importer/xpath_mapper.rb 0 → 100755
... ... @@ -0,0 +1,35 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +begin
  14 + require 'xml/libxml'
  15 +
  16 + # For files with the first line containing field names
  17 + class Solr::Importer::XPathMapper < Solr::Importer::Mapper
  18 + def field_data(doc, xpath)
  19 + doc.find(xpath.to_s).collect do |node|
  20 + case node
  21 + when XML::Attr
  22 + node.value
  23 + when XML::Node
  24 + node.content
  25 + end
  26 + end
  27 + end
  28 + end
  29 +rescue LoadError => e # If we can't load libxml
  30 + class Solr::Importer::XPathMapper
  31 + def initialize(mapping, options={})
  32 + raise "libxml not installed"
  33 + end
  34 + end
  35 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/indexer.rb 0 → 100755
... ... @@ -0,0 +1,52 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Indexer
  14 + attr_reader :solr
  15 +
  16 + # TODO: document options!
  17 + def initialize(data_source, mapper_or_mapping, options={})
  18 + solr_url = options[:solr_url] || ENV["SOLR_URL"] || "http://localhost:8983/solr"
  19 + @solr = Solr::Connection.new(solr_url, options) #TODO - these options contain the solr_url and debug keys also, so tidy up what gets passed
  20 +
  21 + @data_source = data_source
  22 + @mapper = mapper_or_mapping.is_a?(Hash) ? Solr::Importer::Mapper.new(mapper_or_mapping) : mapper_or_mapping
  23 +
  24 + @buffer_docs = options[:buffer_docs]
  25 + @debug = options[:debug]
  26 + end
  27 +
  28 + def index
  29 + buffer = []
  30 + @data_source.each do |record|
  31 + document = @mapper.map(record)
  32 +
  33 + # TODO: check arrity of block, if 3, pass counter as 3rd argument
  34 + yield(record, document) if block_given? # TODO check return of block, if not true then don't index, or perhaps if document.empty?
  35 +
  36 + buffer << document
  37 +
  38 + if !@buffer_docs || buffer.size == @buffer_docs
  39 + add_docs(buffer)
  40 + buffer.clear
  41 + end
  42 + end
  43 + add_docs(buffer) if !buffer.empty?
  44 +
  45 + @solr.commit unless @debug
  46 + end
  47 +
  48 + def add_docs(documents)
  49 + @solr.add(documents) unless @debug
  50 + puts documents.inspect if @debug
  51 + end
  52 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request.rb 0 → 100755
... ... @@ -0,0 +1,26 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +module Solr; module Request; end; end
  14 +require 'solr/request/add_document'
  15 +require 'solr/request/modify_document'
  16 +require 'solr/request/base'
  17 +require 'solr/request/commit'
  18 +require 'solr/request/delete'
  19 +require 'solr/request/ping'
  20 +require 'solr/request/select'
  21 +require 'solr/request/standard'
  22 +require 'solr/request/spellcheck'
  23 +require 'solr/request/dismax'
  24 +require 'solr/request/update'
  25 +require 'solr/request/index_info'
  26 +require 'solr/request/optimize'
... ...
vendor/plugins/acts_as_solr/lib/solr/request/add_document.rb 0 → 100644
... ... @@ -0,0 +1,63 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +require 'solr/request/base'
  15 +require 'solr/document'
  16 +require 'solr/request/update'
  17 +
  18 +class Solr::Request::AddDocument < Solr::Request::Update
  19 +
  20 + # create the request, optionally passing in a Solr::Document
  21 + #
  22 + # request = Solr::Request::AddDocument.new doc
  23 + #
  24 + # as a short cut you can pass in a Hash instead:
  25 + #
  26 + # request = Solr::Request::AddDocument.new :creator => 'Jorge Luis Borges'
  27 + #
  28 + # or an array, to add multiple documents at the same time:
  29 + #
  30 + # request = Solr::Request::AddDocument.new([doc1, doc2, doc3])
  31 +
  32 + def initialize(doc={})
  33 + @docs = []
  34 + if doc.is_a?(Array)
  35 + doc.each { |d| add_doc(d) }
  36 + else
  37 + add_doc(doc)
  38 + end
  39 + end
  40 +
  41 + # returns the request as a string suitable for posting
  42 +
  43 + def to_s
  44 + e = Solr::XML::Element.new 'add'
  45 + for doc in @docs
  46 + e.add_element doc.to_xml
  47 + end
  48 + return e.to_s
  49 + end
  50 +
  51 + private
  52 + def add_doc(doc)
  53 + case doc
  54 + when Hash
  55 + @docs << Solr::Document.new(doc)
  56 + when Solr::Document
  57 + @docs << doc
  58 + else
  59 + raise "must pass in Solr::Document or Hash"
  60 + end
  61 + end
  62 +
  63 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/base.rb 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Request::Base
  14 +
  15 +
  16 + #TODO : Add base support for the debugQuery flag, and such that the response provides debug output easily
  17 +
  18 + # returns either :xml or :ruby depending on what the
  19 + # response type is for a given request
  20 +
  21 + def response_format
  22 + raise "unknown request type: #{self.class}"
  23 + end
  24 +
  25 + def content_type
  26 + 'text/xml; charset=utf-8'
  27 + end
  28 +
  29 + # returns the solr handler or url fragment that can
  30 + # respond to this type of request
  31 +
  32 + def handler
  33 + raise "unknown request type: #{self.class}"
  34 + end
  35 +
  36 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/commit.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +
  15 +class Solr::Request::Commit < Solr::Request::Update
  16 +
  17 + def initialize(options={})
  18 + @wait_searcher = options[:wait_searcher] || true
  19 + @wait_flush = options[:wait_flush] || true
  20 + end
  21 +
  22 +
  23 + def to_s
  24 + e = Solr::XML::Element.new('commit')
  25 + e.attributes['waitSearcher'] = @wait_searcher ? 'true' : 'false'
  26 + e.attributes['waitFlush'] = @wait_flush ? 'true' : 'false'
  27 +
  28 + e.to_s
  29 + end
  30 +
  31 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/delete.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +
  15 +class Solr::Request::Delete < Solr::Request::Update
  16 +
  17 + # A delete request can be for a specific document id
  18 + #
  19 + # request = Solr::Request::Delete.new(:id => 1234)
  20 + #
  21 + # or by query:
  22 + #
  23 + # request = Solr::Request::Delete.new(:query =>
  24 + #
  25 + def initialize(options)
  26 + unless options.kind_of?(Hash) and (options[:id] or options[:query])
  27 + raise Solr::Exception.new("must pass in :id or :query")
  28 + end
  29 + if options[:id] and options[:query]
  30 + raise Solr::Exception.new("can't pass in both :id and :query")
  31 + end
  32 + @document_id = options[:id]
  33 + @query = options[:query]
  34 + end
  35 +
  36 + def to_s
  37 + delete_element = Solr::XML::Element.new('delete')
  38 + if @document_id
  39 + id_element = Solr::XML::Element.new('id')
  40 + id_element.text = @document_id
  41 + delete_element.add_element(id_element)
  42 + elsif @query
  43 + query = Solr::XML::Element.new('query')
  44 + query.text = @query
  45 + delete_element.add_element(query)
  46 + end
  47 + delete_element.to_s
  48 + end
  49 +end
  50 +
... ...
vendor/plugins/acts_as_solr/lib/solr/request/dismax.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Request::Dismax < Solr::Request::Standard
  14 +
  15 + VALID_PARAMS.replace(VALID_PARAMS + [:tie_breaker, :query_fields, :minimum_match, :phrase_fields, :phrase_slop,
  16 + :boost_query, :boost_functions])
  17 +
  18 + def initialize(params)
  19 + @alternate_query = params.delete(:alternate_query)
  20 + @sort_values = params.delete(:sort)
  21 +
  22 + super
  23 +
  24 + @query_type = "dismax"
  25 + end
  26 +
  27 + def to_hash
  28 + hash = super
  29 + hash[:tie] = @params[:tie_breaker]
  30 + hash[:mm] = @params[:minimum_match]
  31 + hash[:qf] = @params[:query_fields]
  32 + hash[:pf] = @params[:phrase_fields]
  33 + hash[:ps] = @params[:phrase_slop]
  34 + hash[:bq] = @params[:boost_query]
  35 + hash[:bf] = @params[:boost_functions]
  36 + hash["q.alt"] = @alternate_query
  37 + # FIXME: 2007-02-13 <coda.hale@gmail.com> -- This code is duplicated in
  38 + # Solr::Request::Standard. It should be refactored into a single location.
  39 + hash[:sort] = @sort_values.collect do |sort|
  40 + key = sort.keys[0]
  41 + "#{key.to_s} #{sort[key] == :descending ? 'desc' : 'asc'}"
  42 + end.join(',') if @sort_values
  43 + return hash
  44 + end
  45 +
  46 +end
0 47 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/request/index_info.rb 0 → 100755
... ... @@ -0,0 +1,22 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Request::IndexInfo < Solr::Request::Select
  14 +
  15 + def handler
  16 + 'admin/luke'
  17 + end
  18 +
  19 + def to_hash
  20 + {:numTerms => 0}.merge(super.to_hash)
  21 + end
  22 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/modify_document.rb 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +require 'solr/request/base'
  15 +require 'solr/document'
  16 +require 'solr/request/update'
  17 +
  18 +class Solr::Request::ModifyDocument < Solr::Request::Update
  19 +
  20 + # Example: ModifyDocument.new(:id => 10, :overwrite => {:field_name => "new value"})
  21 + def initialize(update_data)
  22 + modes = []
  23 + @doc = {}
  24 + [:overwrite, :append, :distinct, :increment, :delete].each do |mode|
  25 + field_data = update_data[mode]
  26 + if field_data
  27 + field_data.each do |field_name, field_value|
  28 + modes << "#{field_name}:#{mode.to_s.upcase}"
  29 + @doc[field_name] = field_value if field_value # if value is nil, omit so it can be removed
  30 + end
  31 + update_data.delete mode
  32 + end
  33 + end
  34 + @mode = modes.join(",")
  35 +
  36 + # only one key should be left over, the id
  37 + @doc[update_data.keys[0].to_s] = update_data.values[0]
  38 + end
  39 +
  40 + # returns the request as a string suitable for posting
  41 + def to_s
  42 + e = Solr::XML::Element.new 'add'
  43 + e.add_element(Solr::Document.new(@doc).to_xml)
  44 + return e.to_s
  45 + end
  46 +
  47 + def handler
  48 + "update?mode=#{@mode}"
  49 + end
  50 +
  51 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/optimize.rb 0 → 100755
... ... @@ -0,0 +1,21 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'solr/xml'
  14 +
  15 +class Solr::Request::Optimize < Solr::Request::Update
  16 +
  17 + def to_s
  18 + Solr::XML::Element.new('optimize').to_s
  19 + end
  20 +
  21 +end
0 22 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/request/ping.rb 0 → 100644
... ... @@ -0,0 +1,36 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +# TODO: Consider something lazy like this?
  14 +# Solr::Request::Ping = Solr::Request.simple_request :format=>:xml, :handler=>'admin/ping'
  15 +# class Solr::Request
  16 +# def self.simple_request(options)
  17 +# Class.new do
  18 +# def response_format
  19 +# options[:format]
  20 +# end
  21 +# def handler
  22 +# options[:handler]
  23 +# end
  24 +# end
  25 +# end
  26 +# end
  27 +
  28 +class Solr::Request::Ping < Solr::Request::Base
  29 + def response_format
  30 + :xml
  31 + end
  32 +
  33 + def handler
  34 + 'admin/ping'
  35 + end
  36 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/select.rb 0 → 100644
... ... @@ -0,0 +1,56 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'erb'
  14 +
  15 +# "Abstract" base class, only useful with subclasses that add parameters
  16 +class Solr::Request::Select < Solr::Request::Base
  17 +
  18 + attr_reader :query_type
  19 +
  20 + def initialize(qt=nil, params={})
  21 + @query_type = qt
  22 + @select_params = params
  23 + end
  24 +
  25 + def response_format
  26 + :ruby
  27 + end
  28 +
  29 + def handler
  30 + 'select'
  31 + end
  32 +
  33 + def content_type
  34 + 'application/x-www-form-urlencoded; charset=utf-8'
  35 + end
  36 +
  37 + def to_hash
  38 + return {:qt => query_type, :wt => 'ruby'}.merge(@select_params)
  39 + end
  40 +
  41 + def to_s
  42 + raw_params = self.to_hash
  43 +
  44 + http_params = []
  45 + raw_params.each do |key,value|
  46 + if value.respond_to? :each
  47 + value.each { |v| http_params << "#{key}=#{ERB::Util::url_encode(v)}" unless v.nil?}
  48 + else
  49 + http_params << "#{key}=#{ERB::Util::url_encode(value)}" unless value.nil?
  50 + end
  51 + end
  52 +
  53 + http_params.join("&")
  54 + end
  55 +
  56 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/spellcheck.rb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Request::Spellcheck < Solr::Request::Select
  14 +
  15 + def initialize(params)
  16 + super('spellchecker')
  17 + @params = params
  18 + end
  19 +
  20 + def to_hash
  21 + hash = super
  22 + hash[:q] = @params[:query]
  23 + hash[:suggestionCount] = @params[:suggestion_count]
  24 + hash[:accuracy] = @params[:accuracy]
  25 + hash[:onlyMorePopular] = @params[:only_more_popular]
  26 + hash[:cmd] = @params[:command]
  27 + return hash
  28 + end
  29 +
  30 +end
0 31 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/request/standard.rb 0 → 100755
... ... @@ -0,0 +1,402 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Request::Standard < Solr::Request::Select
  14 +
  15 + VALID_PARAMS = [:query, :sort, :default_field, :operator, :start, :rows, :shards, :date_facets,
  16 + :filter_queries, :field_list, :debug_query, :explain_other, :facets, :highlighting, :mlt]
  17 +
  18 + def initialize(params)
  19 + super('standard')
  20 +
  21 + raise "Invalid parameters: #{(params.keys - VALID_PARAMS).join(',')}" unless
  22 + (params.keys - VALID_PARAMS).empty?
  23 +
  24 + raise ":query parameter required" unless params[:query]
  25 +
  26 + @params = params.dup
  27 +
  28 + # Validate operator
  29 + if params[:operator]
  30 + raise "Only :and/:or operators allowed" unless
  31 + [:and, :or].include?(params[:operator])
  32 +
  33 + @params[:operator] = params[:operator].to_s.upcase
  34 + end
  35 +
  36 + # Validate start, rows can be transformed to ints
  37 + @params[:start] = params[:start].to_i if params[:start]
  38 + @params[:rows] = params[:rows].to_i if params[:rows]
  39 +
  40 + @params[:field_list] ||= ["*","score"]
  41 +
  42 + @params[:shards] ||= []
  43 + end
  44 +
  45 + def to_hash
  46 + hash = {}
  47 +
  48 + # standard request param processing
  49 + sort = @params[:sort].collect do |sort|
  50 + key = sort.keys[0]
  51 + "#{key.to_s} #{sort[key] == :descending ? 'desc' : 'asc'}"
  52 + end.join(',') if @params[:sort]
  53 + hash[:q] = sort ? "#{@params[:query]};#{sort}" : @params[:query]
  54 + hash["q.op"] = @params[:operator]
  55 + hash[:df] = @params[:default_field]
  56 +
  57 + # common parameter processing
  58 + hash[:start] = @params[:start]
  59 + hash[:rows] = @params[:rows]
  60 + hash[:fq] = @params[:filter_queries]
  61 + hash[:fl] = @params[:field_list].join(',')
  62 + hash[:debugQuery] = @params[:debug_query]
  63 + hash[:explainOther] = @params[:explain_other]
  64 + hash[:shards] = @params[:shards].join(',') unless @params[:shards].empty?
  65 +
  66 + # facet parameter processing
  67 + if @params[:facets]
  68 + # TODO need validation of all that is under the :facets Hash too
  69 + hash[:facet] = true
  70 + hash["facet.field"] = []
  71 + hash["facet.query"] = @params[:facets][:queries]
  72 + hash["facet.sort"] = (@params[:facets][:sort] == :count) if @params[:facets][:sort]
  73 + hash["facet.limit"] = @params[:facets][:limit]
  74 + hash["facet.missing"] = @params[:facets][:missing]
  75 + hash["facet.mincount"] = @params[:facets][:mincount]
  76 + hash["facet.prefix"] = @params[:facets][:prefix]
  77 + hash["facet.offset"] = @params[:facets][:offset]
  78 + if @params[:facets][:fields] # facet fields are optional (could be facet.query only)
  79 + @params[:facets][:fields].each do |f|
  80 + if f.kind_of? Hash
  81 + key = f.keys[0]
  82 + value = f[key]
  83 + hash["facet.field"] << key
  84 + hash["f.#{key}.facet.sort"] = (value[:sort] == :count) if value[:sort]
  85 + hash["f.#{key}.facet.limit"] = value[:limit]
  86 + hash["f.#{key}.facet.missing"] = value[:missing]
  87 + hash["f.#{key}.facet.mincount"] = value[:mincount]
  88 + hash["f.#{key}.facet.prefix"] = value[:prefix]
  89 + hash["f.#{key}.facet.offset"] = value[:offset]
  90 + else
  91 + hash["facet.field"] << f
  92 + end
  93 + end
  94 + end
  95 +
  96 + if @params[:date_facets]
  97 + hash["facet.date"] = []
  98 + if @params[:date_facets][:fields]
  99 + @params[:date_facets][:fields].each do |f|
  100 + if f.kind_of? Hash
  101 + key = f.keys[0]
  102 + hash["facet.date"] << key
  103 + f[key].each { |k, v|
  104 + hash["f.#{key}.facet.date.#{k}"] = v
  105 + }
  106 + else
  107 + hash["facet.date"] << f
  108 + end
  109 + end
  110 + end
  111 + hash["facet.date.start"] = @params[:date_facets][:start]
  112 + hash["facet.date.end"] = @params[:date_facets][:end]
  113 + hash["facet.date.gap"] = @params[:date_facets][:gap]
  114 + hash["facet.date.other"] = @params[:date_facets][:other]
  115 + hash["facet.date.hardend"] = @params[:date_facets][:hardend]
  116 + if @params[:date_facets][:filter]
  117 + if hash[:fq]
  118 + hash[:fq] << @params[:date_facets][:filter]
  119 + else
  120 + hash[:fq] = @params[:date_facets][:filter]
  121 + end
  122 + end
  123 + end
  124 + end
  125 +
  126 + # highlighting parameter processing - http://wiki.apache.org/solr/HighlightingParameters
  127 + if @params[:highlighting]
  128 + hash[:hl] = true
  129 + hash["hl.fl"] = @params[:highlighting][:field_list].join(',') if @params[:highlighting][:field_list]
  130 +
  131 + snippets = @params[:highlighting][:max_snippets]
  132 + if snippets
  133 + if snippets.kind_of? Hash
  134 + if snippets[:default]
  135 + hash["hl.snippets"] = snippets[:default]
  136 + end
  137 + if snippets[:fields]
  138 + snippets[:fields].each do |k,v|
  139 + hash["f.#{k}.hl.snippets"] = v
  140 + end
  141 + end
  142 + else
  143 + hash["hl.snippets"] = snippets
  144 + end
  145 + end
  146 +
  147 + fragsize = @params[:highlighting][:fragment_size]
  148 + if fragsize
  149 + if fragsize.kind_of? Hash
  150 + if fragsize[:default]
  151 + hash["hl.fragsize"] = fragsize[:default]
  152 + end
  153 + if fragsize[:fields]
  154 + fragsize[:fields].each do |k,v|
  155 + hash["f.#{k}.hl.fragsize"] = v
  156 + end
  157 + end
  158 + else
  159 + hash["hl.fragsize"] = fragsize
  160 + end
  161 + end
  162 +
  163 + rfm = @params[:highlighting][:require_field_match]
  164 + if nil != rfm
  165 + if rfm.kind_of? Hash
  166 + if nil != rfm[:default]
  167 + hash["hl.requireFieldMatch"] = rfm[:default]
  168 + end
  169 + if rfm[:fields]
  170 + rfm[:fields].each do |k,v|
  171 + hash["f.#{k}.hl.requireFieldMatch"] = v
  172 + end
  173 + end
  174 + else
  175 + hash["hl.requireFieldMatch"] = rfm
  176 + end
  177 + end
  178 +
  179 + mac = @params[:highlighting][:max_analyzed_chars]
  180 + if mac
  181 + if mac.kind_of? Hash
  182 + if mac[:default]
  183 + hash["hl.maxAnalyzedChars"] = mac[:default]
  184 + end
  185 + if mac[:fields]
  186 + mac[:fields].each do |k,v|
  187 + hash["f.#{k}.hl.maxAnalyzedChars"] = v
  188 + end
  189 + end
  190 + else
  191 + hash["hl.maxAnalyzedChars"] = mac
  192 + end
  193 + end
  194 +
  195 + prefix = @params[:highlighting][:prefix]
  196 + if prefix
  197 + if prefix.kind_of? Hash
  198 + if prefix[:default]
  199 + hash["hl.simple.pre"] = prefix[:default]
  200 + end
  201 + if prefix[:fields]
  202 + prefix[:fields].each do |k,v|
  203 + hash["f.#{k}.hl.simple.pre"] = v
  204 + end
  205 + end
  206 + else
  207 + hash["hl.simple.pre"] = prefix
  208 + end
  209 + end
  210 +
  211 + suffix = @params[:highlighting][:suffix]
  212 + if suffix
  213 + if suffix.kind_of? Hash
  214 + if suffix[:default]
  215 + hash["hl.simple.post"] = suffix[:default]
  216 + end
  217 + if suffix[:fields]
  218 + suffix[:fields].each do |k,v|
  219 + hash["f.#{k}.hl.simple.post"] = v
  220 + end
  221 + end
  222 + else
  223 + hash["hl.simple.post"] = suffix
  224 + end
  225 + end
  226 +
  227 + formatter = @params[:highlighting][:formatter]
  228 + if formatter
  229 + if formatter.kind_of? Hash
  230 + if formatter[:default]
  231 + hash["hl.formatter"] = formatter[:default]
  232 + end
  233 + if formatter[:fields]
  234 + formatter[:fields].each do |k,v|
  235 + hash["f.#{k}.hl.formatter"] = v
  236 + end
  237 + end
  238 + else
  239 + hash["hl.formatter"] = formatter
  240 + end
  241 + end
  242 +
  243 + fragmenter = @params[:highlighting][:fragmenter]
  244 + if fragmenter
  245 + if fragmenter.kind_of? Hash
  246 + if fragmenter[:default]
  247 + hash["hl.fragmenter"] = fragmenter[:default]
  248 + end
  249 + if fragmenter[:fields]
  250 + fragmenter[:fields].each do |k,v|
  251 + hash["f.#{k}.hl.fragmenter"] = v
  252 + end
  253 + end
  254 + else
  255 + hash["hl.fragmenter"] = fragmenter
  256 + end
  257 + end
  258 +
  259 + merge_contiguous = @params[:highlighting][:merge_contiguous]
  260 + if nil != merge_contiguous
  261 + if merge_contiguous.kind_of? Hash
  262 + if nil != merge_contiguous[:default]
  263 + hash["hl.mergeContiguous"] = merge_contiguous[:default]
  264 + end
  265 + if merge_contiguous[:fields]
  266 + merge_contiguous[:fields].each do |k,v|
  267 + hash["f.#{k}.hl.mergeContiguous"] = v
  268 + end
  269 + end
  270 + else
  271 + hash["hl.mergeContiguous"] = merge_contiguous
  272 + end
  273 + end
  274 +
  275 + increment = @params[:highlighting][:increment]
  276 + if increment
  277 + if increment.kind_of? Hash
  278 + if increment[:default]
  279 + hash["hl.increment"] = increment[:default]
  280 + end
  281 + if increment[:fields]
  282 + increment[:fields].each do |k,v|
  283 + hash["f.#{k}.hl.increment"] = v
  284 + end
  285 + end
  286 + else
  287 + hash["hl.increment"] = increment
  288 + end
  289 + end
  290 +
  291 + # support "old style"
  292 + alternate_fields = @params[:highlighting][:alternate_fields]
  293 + if alternate_fields
  294 + alternate_fields.each do |f,v|
  295 + hash["f.#{f}.hl.alternateField"] = v
  296 + end
  297 + end
  298 +
  299 + alternate_field = @params[:highlighting][:alternate_field]
  300 + if alternate_field
  301 + if alternate_field.kind_of? Hash
  302 + if alternate_field[:default]
  303 + hash["hl.alternateField"] = alternate_field[:default]
  304 + end
  305 + if alternate_field[:fields]
  306 + alternate_field[:fields].each do |k,v|
  307 + hash["f.#{k}.hl.alternateField"] = v
  308 + end
  309 + end
  310 + else
  311 + hash["hl.alternateField"] = alternate_field
  312 + end
  313 + end
  314 +
  315 + mafl = @params[:highlighting][:max_alternate_field_length]
  316 + if mafl
  317 + if mafl.kind_of? Hash
  318 + if mafl[:default]
  319 + hash["hl.maxAlternateFieldLength"] = mafl[:default]
  320 + end
  321 + if mafl[:fields]
  322 + mafl[:fields].each do |k,v|
  323 + hash["f.#{k}.hl.maxAlternateFieldLength"] = v
  324 + end
  325 + else
  326 + # support "old style"
  327 + mafl.each do |k,v|
  328 + hash["f.#{k}.hl.maxAlternateFieldLength"] = v
  329 + end
  330 + end
  331 + else
  332 + hash["hl.maxAlternateFieldLength"] = mafl
  333 + end
  334 + end
  335 +
  336 + hash["hl.usePhraseHighlighter"] = @params[:highlighting][:use_phrase_highlighter]
  337 +
  338 + regex = @params[:highlighting][:regex]
  339 + if regex
  340 + if regex[:slop]
  341 + if regex[:slop].kind_of? Hash
  342 + if regex[:slop][:default]
  343 + hash["hl.regex.slop"] = regex[:slop][:default]
  344 + end
  345 + if regex[:slop][:fields]
  346 + regex[:slop][:fields].each do |k,v|
  347 + hash["f.#{k}.hl.regex.slop"] = v
  348 + end
  349 + end
  350 + else
  351 + hash["hl.regex.slop"] = regex[:slop]
  352 + end
  353 + end
  354 + if regex[:pattern]
  355 + if regex[:pattern].kind_of? Hash
  356 + if regex[:pattern][:default]
  357 + hash["hl.regex.pattern"] = regex[:pattern][:default]
  358 + end
  359 + if regex[:pattern][:fields]
  360 + regex[:pattern][:fields].each do |k,v|
  361 + hash["f.#{k}.hl.regex.pattern"] = v
  362 + end
  363 + end
  364 + else
  365 + hash["hl.regex.pattern"] = regex[:pattern]
  366 + end
  367 + end
  368 + if regex[:max_analyzed_chars]
  369 + if regex[:max_analyzed_chars].kind_of? Hash
  370 + if regex[:max_analyzed_chars][:default]
  371 + hash["hl.regex.maxAnalyzedChars"] = regex[:max_analyzed_chars][:default]
  372 + end
  373 + if regex[:max_analyzed_chars][:fields]
  374 + regex[:max_analyzed_chars][:fields].each do |k,v|
  375 + hash["f.#{k}.hl.regex.maxAnalyzedChars"] = v
  376 + end
  377 + end
  378 + else
  379 + hash["hl.regex.maxAnalyzedChars"] = regex[:max_analyzed_chars]
  380 + end
  381 + end
  382 + end
  383 +
  384 + end
  385 +
  386 + if @params[:mlt]
  387 + hash[:mlt] = true
  388 + hash["mlt.count"] = @params[:mlt][:count]
  389 + hash["mlt.fl"] = @params[:mlt][:field_list].join(',')
  390 + hash["mlt.mintf"] = @params[:mlt][:min_term_freq]
  391 + hash["mlt.mindf"] = @params[:mlt][:min_doc_freq]
  392 + hash["mlt.minwl"] = @params[:mlt][:min_word_length]
  393 + hash["mlt.maxwl"] = @params[:mlt][:max_word_length]
  394 + hash["mlt.maxqt"] = @params[:mlt][:max_query_terms]
  395 + hash["mlt.maxntp"] = @params[:mlt][:max_tokens_parsed]
  396 + hash["mlt.boost"] = @params[:mlt][:boost]
  397 + end
  398 +
  399 + hash.merge(super.to_hash)
  400 + end
  401 +
  402 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/request/update.rb 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +# a parent class for all requests that go through the solr update handler
  14 +# TODO: Use new xml update handler for better error responses
  15 +class Solr::Request::Update < Solr::Request::Base
  16 + def response_format
  17 + :xml
  18 + end
  19 +
  20 + def handler
  21 + 'update'
  22 + end
  23 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response.rb 0 → 100755
... ... @@ -0,0 +1,27 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +module Solr; module Response; end; end
  14 +require 'solr/response/base'
  15 +require 'solr/response/xml'
  16 +require 'solr/response/ruby'
  17 +require 'solr/response/ping'
  18 +require 'solr/response/add_document'
  19 +require 'solr/response/modify_document'
  20 +require 'solr/response/standard'
  21 +require 'solr/response/spellcheck'
  22 +require 'solr/response/dismax'
  23 +require 'solr/response/commit'
  24 +require 'solr/response/delete'
  25 +require 'solr/response/index_info'
  26 +require 'solr/response/optimize'
  27 +require 'solr/response/select'
0 28 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/response/add_document.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::AddDocument < Solr::Response::Xml
  14 + def initialize(xml)
  15 + super
  16 + end
  17 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/base.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Base
  14 + attr_reader :raw_response
  15 +
  16 + def initialize(raw_response)
  17 + @raw_response = raw_response
  18 + end
  19 +
  20 + # factory method for creating a Solr::Response::* from
  21 + # a request and the raw response content
  22 + def self.make_response(request, raw)
  23 +
  24 + # make sure response format seems sane
  25 + unless [:xml, :ruby].include?(request.response_format)
  26 + raise Solr::Exception.new("unknown response format: #{request.response_format}" )
  27 + end
  28 +
  29 + # TODO: Factor out this case... perhaps the request object should provide the response class instead? Or dynamically align by class name?
  30 + # Maybe the request itself could have the response handling features that get mixed in with a single general purpose response object?
  31 +
  32 + begin
  33 + klass = eval(request.class.name.sub(/Request/,'Response'))
  34 + rescue NameError
  35 + raise Solr::Exception.new("unknown request type: #{request.class}")
  36 + else
  37 + klass.new(raw)
  38 + end
  39 +
  40 + end
  41 +
  42 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/commit.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'rexml/xpath'
  14 +
  15 +class Solr::Response::Commit < Solr::Response::Xml
  16 +end
  17 +
... ...
vendor/plugins/acts_as_solr/lib/solr/response/delete.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Delete < Solr::Response::Xml; end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/dismax.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +class Solr::Response::Dismax < Solr::Response::Standard
  2 + # no need for special processing
  3 +
  4 + # FIXME: 2007-02-07 <coda.hale@gmail.com> -- The existence of this class indicates that
  5 + # the Request/Response pair architecture is a little hinky. Perhaps we could refactor
  6 + # out some of the most common functionality -- Common Query Parameters, Highlighting Parameters,
  7 + # Simple Facet Parameters, etc. -- into modules?
  8 +end
0 9 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/response/index_info.rb 0 → 100755
... ... @@ -0,0 +1,26 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::IndexInfo < Solr::Response::Ruby
  14 + def initialize(ruby_code)
  15 + super
  16 + end
  17 +
  18 + def num_docs
  19 + return @data['index']['numDocs']
  20 + end
  21 +
  22 + def field_names
  23 + return @data['fields'].keys
  24 + end
  25 +
  26 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/modify_document.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::ModifyDocument < Solr::Response::Xml
  14 + def initialize(xml)
  15 + super
  16 + end
  17 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/optimize.rb 0 → 100755
... ... @@ -0,0 +1,14 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Optimize < Solr::Response::Commit
  14 +end
0 15 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr/response/ping.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'rexml/xpath'
  14 +
  15 +class Solr::Response::Ping < Solr::Response::Xml
  16 +
  17 + def initialize(xml)
  18 + super
  19 + @ok = REXML::XPath.first(@doc, './solr/ping') ? true : false
  20 + end
  21 +
  22 + # returns true or false depending on whether the ping
  23 + # was successful or not
  24 + def ok?
  25 + @ok
  26 + end
  27 +
  28 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/ruby.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Ruby < Solr::Response::Base
  14 + attr_reader :data, :header
  15 +
  16 + def initialize(ruby_code)
  17 + super
  18 + begin
  19 + #TODO: what about pulling up data/header/response to ResponseBase,
  20 + # or maybe a new middle class like SelectResponseBase since
  21 + # all Select queries return this same sort of stuff??
  22 + # XML (&wt=xml) and Ruby (&wt=ruby) responses contain exactly the same structure.
  23 + # a goal of solrb is to make it irrelevant which gets used under the hood,
  24 + # but favor Ruby responses.
  25 + @data = eval(ruby_code)
  26 + @header = @data['responseHeader']
  27 + raise "response should be a hash" unless @data.kind_of? Hash
  28 + raise "response header missing" unless @header.kind_of? Hash
  29 + rescue SyntaxError => e
  30 + raise Solr::Exception.new("invalid ruby code: #{e}")
  31 + end
  32 + end
  33 +
  34 + def ok?
  35 + @header['status'] == 0
  36 + end
  37 +
  38 + def query_time
  39 + @header['QTime']
  40 + end
  41 +
  42 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/select.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Select < Solr::Response::Ruby
  14 + def initialize(ruby_code)
  15 + super
  16 + end
  17 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/spellcheck.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Spellcheck < Solr::Response::Ruby
  14 + attr_reader :suggestions
  15 +
  16 + def initialize(ruby_code)
  17 + super
  18 + @suggestions = @data['suggestions']
  19 + end
  20 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/standard.rb 0 → 100644
... ... @@ -0,0 +1,60 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Response::Standard < Solr::Response::Ruby
  14 + FacetValue = Struct.new(:name, :value)
  15 + include Enumerable
  16 +
  17 + def initialize(ruby_code)
  18 + super
  19 + @response = @data['response']
  20 + raise "response section missing" unless @response.kind_of? Hash
  21 + end
  22 +
  23 + def total_hits
  24 + @response['numFound']
  25 + end
  26 +
  27 + def start
  28 + @response['start']
  29 + end
  30 +
  31 + def hits
  32 + @response['docs']
  33 + end
  34 +
  35 + def max_score
  36 + @response['maxScore']
  37 + end
  38 +
  39 + # TODO: consider the use of json.nl parameter
  40 + def field_facets(field)
  41 + facets = []
  42 + values = @data['facet_counts']['facet_fields'][field]
  43 + Solr::Util.paired_array_each(values) do |key, value|
  44 + facets << FacetValue.new(key, value)
  45 + end
  46 +
  47 + facets
  48 + end
  49 +
  50 + def highlighted(id, field)
  51 + @data['highlighting'][id.to_s][field.to_s] rescue nil
  52 + end
  53 +
  54 + # supports enumeration of hits
  55 + # TODO revisit - should this iterate through *all* hits by re-requesting more?
  56 + def each
  57 + @response['docs'].each {|hit| yield hit}
  58 + end
  59 +
  60 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/response/xml.rb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +require 'rexml/document'
  14 +require 'solr/exception'
  15 +
  16 +class Solr::Response::Xml < Solr::Response::Base
  17 + attr_reader :doc, :status_code, :status_message
  18 +
  19 + def initialize(xml)
  20 + super
  21 + # parse the xml
  22 + @doc = REXML::Document.new(xml)
  23 +
  24 + # look for the result code and string
  25 + # <?xml version="1.0" encoding="UTF-8"?>
  26 + # <response>
  27 + # <lst name="responseHeader"><int name="status">0</int><int name="QTime">2</int></lst>
  28 + # </response>
  29 + result = REXML::XPath.first(@doc, './response/lst[@name="responseHeader"]/int[@name="status"]')
  30 + if result
  31 + @status_code = result.text
  32 + @status_message = result.text # TODO: any need for a message?
  33 + end
  34 + rescue REXML::ParseException => e
  35 + raise Solr::Exception.new("invalid response xml: #{e}")
  36 + end
  37 +
  38 + def ok?
  39 + return @status_code == '0'
  40 + end
  41 +
  42 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/solrtasks.rb 0 → 100755
... ... @@ -0,0 +1,27 @@
  1 +#!/usr/bin/env ruby
  2 +# The ASF licenses this file to You under the Apache License, Version 2.0
  3 +# (the "License"); you may not use this file except in compliance with
  4 +# the License. You may obtain a copy of the License at
  5 +#
  6 +# http://www.apache.org/licenses/LICENSE-2.0
  7 +#
  8 +# Unless required by applicable law or agreed to in writing, software
  9 +# distributed under the License is distributed on an "AS IS" BASIS,
  10 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11 +# See the License for the specific language governing permissions and
  12 +# limitations under the License.
  13 +
  14 +# TODO: fill out Solr tasks: start, stop, ping, optimize, etc.
  15 +
  16 +require 'rake'
  17 +require 'rake/tasklib'
  18 +
  19 +module Solr
  20 + namespace :solr do
  21 + desc "Start Solr"
  22 + task :start do
  23 + # TODO: actually start it up!
  24 + puts "Starting..."
  25 + end
  26 + end
  27 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/util.rb 0 → 100755
... ... @@ -0,0 +1,32 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +class Solr::Util
  14 + # paired_array_each([key1,value1,key2,value2]) yields twice:
  15 + # |key1,value1| and |key2,value2|
  16 + def self.paired_array_each(a, &block)
  17 + 0.upto(a.size / 2 - 1) do |i|
  18 + n = i * 2
  19 + yield(a[n], a[n+1])
  20 + end
  21 + end
  22 +
  23 + # paired_array_to_hash([key1,value1,key2,value2]) => {key1 => value1, key2, value2}
  24 + def self.paired_array_to_hash(a)
  25 + Hash[*a]
  26 + end
  27 +
  28 + def self.query_parser_escape(string)
  29 + # backslash prefix everything that isn't a word character
  30 + string.gsub(/(\W)/,'\\\\\1')
  31 + end
  32 +end
... ...
vendor/plugins/acts_as_solr/lib/solr/xml.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +module Solr::XML
  14 +end
  15 +
  16 +begin
  17 +
  18 + # If we can load rubygems and libxml-ruby...
  19 + require 'rubygems'
  20 + require 'xml/libxml'
  21 + raise "acts_as_solr requires libxml-ruby 0.7 or greater" unless XML::Node.public_instance_methods.include?("attributes")
  22 +
  23 + # then make a few modifications to XML::Node so it can stand in for REXML::Element
  24 + class XML::Node
  25 + # element.add_element(another_element) should work
  26 + alias_method :add_element, :<<
  27 +
  28 +
  29 + # element.text = "blah" should work
  30 + def text=(x)
  31 + self << x.to_s
  32 + end
  33 + end
  34 +
  35 + # And use XML::Node for our XML generation
  36 + Solr::XML::Element = XML::Node
  37 +
  38 +rescue LoadError => e # If we can't load either rubygems or libxml-ruby
  39 + puts "Requiring REXML"
  40 + # Just use REXML.
  41 + require 'rexml/document'
  42 + Solr::XML::Element = REXML::Element
  43 +
  44 +end
0 45 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/solr_fixtures.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +module ActsAsSolr
  2 +
  3 + class SolrFixtures
  4 + def self.load(table_names)
  5 + [table_names].flatten.map { |n| n.to_s }.each do |table_name|
  6 + klass = instance_eval(File.split(table_name.to_s).last.to_s.gsub('_',' ').split(" ").collect{|w| w.capitalize}.to_s.singularize)
  7 + klass.rebuild_solr_index if klass.respond_to?(:rebuild_solr_index)
  8 + end
  9 + ActsAsSolr::Post.execute(Solr::Request::Commit.new)
  10 + end
  11 + end
  12 +
  13 +end
0 14 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/tasks/database.rake 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +require File.dirname(__FILE__) + '/../solr_fixtures'
  2 +
  3 +namespace :db do
  4 + namespace :fixtures do
  5 + desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y"
  6 + task :load => :environment do
  7 + begin
  8 + ActsAsSolr::Post.execute(Solr::Request::Delete.new(:query => "*:*"))
  9 + ActsAsSolr::Post.execute(Solr::Request::Commit.new)
  10 + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
  11 + ActsAsSolr::SolrFixtures.load(File.basename(fixture_file, '.*'))
  12 + end
  13 + puts "The fixtures loaded have been added to Solr"
  14 + rescue
  15 + end
  16 + end
  17 + end
  18 +end
0 19 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/lib/tasks/solr.rake 0 → 100644
... ... @@ -0,0 +1,137 @@
  1 +require 'rubygems'
  2 +require 'rake'
  3 +require 'net/http'
  4 +require 'active_record'
  5 +
  6 +namespace :solr do
  7 +
  8 + desc 'Starts Solr. Options accepted: RAILS_ENV=your_env, PORT=XX. Defaults to development if none.'
  9 + task :start do
  10 + require "#{File.dirname(__FILE__)}/../../config/solr_environment.rb"
  11 + begin
  12 + n = Net::HTTP.new('127.0.0.1', SOLR_PORT)
  13 + n.request_head('/').value
  14 +
  15 + rescue Net::HTTPServerException #responding
  16 + puts "Port #{SOLR_PORT} in use" and return
  17 +
  18 + rescue Errno::ECONNREFUSED #not responding
  19 + Dir.chdir(SOLR_PATH) do
  20 + pid = fork do
  21 + #STDERR.close
  22 + exec "java #{SOLR_JVM_OPTIONS} -Dsolr.data.dir=#{SOLR_DATA_PATH} -Djetty.logs=#{SOLR_LOGS_PATH} -Djetty.port=#{SOLR_PORT} -jar start.jar"
  23 + end
  24 + sleep(5)
  25 + File.open("#{SOLR_PIDS_PATH}/#{ENV['RAILS_ENV']}_pid", "w"){ |f| f << pid}
  26 + puts "#{ENV['RAILS_ENV']} Solr started successfully on #{SOLR_PORT}, pid: #{pid}."
  27 + end
  28 + end
  29 + end
  30 +
  31 + desc 'Stops Solr. Specify the environment by using: RAILS_ENV=your_env. Defaults to development if none.'
  32 + task :stop do
  33 + require "#{File.dirname(__FILE__)}/../../config/solr_environment.rb"
  34 + fork do
  35 + file_path = "#{SOLR_PIDS_PATH}/#{ENV['RAILS_ENV']}_pid"
  36 + if File.exists?(file_path)
  37 + File.open(file_path, "r") do |f|
  38 + pid = f.readline
  39 + Process.kill('TERM', pid.to_i)
  40 + end
  41 + File.unlink(file_path)
  42 + Rake::Task["solr:destroy_index"].invoke if ENV['RAILS_ENV'] == 'test'
  43 + puts "Solr shutdown successfully."
  44 + else
  45 + puts "PID file not found at #{file_path}. Either Solr is not running or no PID file was written."
  46 + end
  47 + end
  48 + end
  49 +
  50 + desc 'Remove Solr index'
  51 + task :destroy_index do
  52 + require "#{File.dirname(__FILE__)}/../../config/solr_environment.rb"
  53 + raise "In production mode. I'm not going to delete the index, sorry." if ENV['RAILS_ENV'] == "production"
  54 + if File.exists?("#{SOLR_DATA_PATH}")
  55 + Dir["#{SOLR_DATA_PATH}/index/*"].each{|f| File.unlink(f)}
  56 + Dir.rmdir("#{SOLR_DATA_PATH}/index")
  57 + puts "Index files removed under " + ENV['RAILS_ENV'] + " environment"
  58 + end
  59 + end
  60 +
  61 + # this task is by Henrik Nyh
  62 + # http://henrik.nyh.se/2007/06/rake-task-to-reindex-models-for-acts_as_solr
  63 + desc %{Reindexes data for all acts_as_solr models. Clears index first to get rid of orphaned records and optimizes index afterwards. RAILS_ENV=your_env to set environment. ONLY=book,person,magazine to only reindex those models; EXCEPT=book,magazine to exclude those models. START_SERVER=true to solr:start before and solr:stop after. BATCH=123 to post/commit in batches of that size: default is 300. CLEAR=false to not clear the index first; OPTIMIZE=false to not optimize the index afterwards.}
  64 + task :reindex => :environment do
  65 + require "#{File.dirname(__FILE__)}/../../config/solr_environment.rb"
  66 +
  67 + includes = env_array_to_constants('ONLY')
  68 + if includes.empty?
  69 + includes = Dir.glob("#{RAILS_ROOT}/app/models/*.rb").map { |path| File.basename(path, ".rb").camelize.constantize }
  70 + end
  71 + excludes = env_array_to_constants('EXCEPT')
  72 + includes -= excludes
  73 +
  74 + optimize = env_to_bool('OPTIMIZE', true)
  75 + start_server = env_to_bool('START_SERVER', false)
  76 + clear_first = env_to_bool('CLEAR', true)
  77 + batch_size = ENV['BATCH'].to_i.nonzero? || 300
  78 + debug_output = env_to_bool("DEBUG", false)
  79 +
  80 + RAILS_DEFAULT_LOGGER.level = ActiveSupport::BufferedLogger::INFO unless debug_output
  81 +
  82 + if start_server
  83 + puts "Starting Solr server..."
  84 + Rake::Task["solr:start"].invoke
  85 + end
  86 +
  87 + # Disable solr_optimize
  88 + module ActsAsSolr::CommonMethods
  89 + def blank() end
  90 + alias_method :deferred_solr_optimize, :solr_optimize
  91 + alias_method :solr_optimize, :blank
  92 + end
  93 +
  94 + models = includes.select { |m| m.respond_to?(:rebuild_solr_index) }
  95 + models.each do |model|
  96 +
  97 + if clear_first
  98 + puts "Clearing index for #{model}..."
  99 + ActsAsSolr::Post.execute(Solr::Request::Delete.new(:query => "#{model.solr_configuration[:type_field]}:#{model}"))
  100 + ActsAsSolr::Post.execute(Solr::Request::Commit.new)
  101 + end
  102 +
  103 + puts "Rebuilding index for #{model}..."
  104 + model.rebuild_solr_index(batch_size)
  105 +
  106 + end
  107 +
  108 + if models.empty?
  109 + puts "There were no models to reindex."
  110 + elsif optimize
  111 + puts "Optimizing..."
  112 + models.last.deferred_solr_optimize
  113 + end
  114 +
  115 + if start_server
  116 + puts "Shutting down Solr server..."
  117 + Rake::Task["solr:stop"].invoke
  118 + end
  119 +
  120 + end
  121 +
  122 + def env_array_to_constants(env)
  123 + env = ENV[env] || ''
  124 + env.split(/\s*,\s*/).map { |m| m.singularize.camelize.constantize }.uniq
  125 + end
  126 +
  127 + def env_to_bool(env, default)
  128 + env = ENV[env] || ''
  129 + case env
  130 + when /^true$/i then true
  131 + when /^false$/i then false
  132 + else default
  133 + end
  134 + end
  135 +
  136 +end
  137 +
... ...
vendor/plugins/acts_as_solr/lib/tasks/test.rake 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +require 'active_record'
  2 +
  3 +namespace :test do
  4 + task :migrate do
  5 + ActiveRecord::Migrator.migrate("test/db/migrate/", ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
  6 + end
  7 +end
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/admin-extra.html 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +<!--
  2 + Licensed to the Apache Software Foundation (ASF) under one or more
  3 + contributor license agreements. See the NOTICE file distributed with
  4 + this work for additional information regarding copyright ownership.
  5 + The ASF licenses this file to You under the Apache License, Version 2.0
  6 + (the "License"); you may not use this file except in compliance with
  7 + the License. You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +-->
  17 +
  18 +<!-- The content of this page will be statically included into the top
  19 +of the admin page. Uncomment this as an example to see there the content
  20 +will show up.
  21 +
  22 +<hr>
  23 +<i>This line will appear before the first table</i>
  24 +<tr>
  25 +<td colspan="2">
  26 +This row will be appended to the end of the first table
  27 +</td>
  28 +</tr>
  29 +<hr>
  30 +
  31 +-->
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/protwords.txt 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +#-----------------------------------------------------------------------
  14 +# Use a protected word file to protect against the stemmer reducing two
  15 +# unrelated words to the same base word.
  16 +
  17 +# Some non-words that normally won't be encountered,
  18 +# just to test that they won't be stemmed.
  19 +dontstems
  20 +zwhacky
  21 +
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/schema.xml 0 → 100644
... ... @@ -0,0 +1,126 @@
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 + Licensed to the Apache Software Foundation (ASF) under one or more
  4 + contributor license agreements. See the NOTICE file distributed with
  5 + this work for additional information regarding copyright ownership.
  6 + The ASF licenses this file to You under the Apache License, Version 2.0
  7 + (the "License"); you may not use this file except in compliance with
  8 + the License. You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +-->
  18 +
  19 +<!--
  20 + This is the Solr schema file. This file should be named "schema.xml" and
  21 + should be in the conf directory under the solr home
  22 + (i.e. ./solr/conf/schema.xml by default)
  23 + or located where the classloader for the Solr webapp can find it.
  24 +
  25 + This example schema is the recommended starting point for users.
  26 + It should be kept correct and concise, usable out-of-the-box.
  27 +
  28 + For more information, on how to customize this file, please see
  29 + http://wiki.apache.org/solr/SchemaXml
  30 +-->
  31 +
  32 +<schema name="acts_as_solr" version="0.9">
  33 + <types>
  34 + <fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="false"/>
  35 + <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="false"/>
  36 + <fieldType name="integer" class="solr.IntField" omitNorms="false"/>
  37 + <fieldType name="long" class="solr.LongField" omitNorms="false"/>
  38 + <fieldType name="float" class="solr.FloatField" omitNorms="false"/>
  39 + <fieldType name="double" class="solr.DoubleField" omitNorms="false"/>
  40 + <fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="false"/>
  41 + <fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="false"/>
  42 + <fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="false"/>
  43 + <fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="false"/>
  44 + <fieldType name="date" class="solr.DateField" sortMissingLast="true" omitNorms="false"/>
  45 + <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
  46 + <analyzer>
  47 + <tokenizer class="solr.WhitespaceTokenizerFactory"/>
  48 + </analyzer>
  49 + </fieldType>
  50 +
  51 + <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
  52 + <analyzer type="index">
  53 + <tokenizer class="solr.WhitespaceTokenizerFactory"/>
  54 + <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
  55 + <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
  56 + <filter class="solr.LowerCaseFilterFactory"/>
  57 + <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
  58 + <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
  59 + </analyzer>
  60 + <analyzer type="query">
  61 + <tokenizer class="solr.WhitespaceTokenizerFactory"/>
  62 + <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
  63 + <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
  64 + <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/>
  65 + <filter class="solr.LowerCaseFilterFactory"/>
  66 + <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
  67 + <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
  68 + </analyzer>
  69 + </fieldType>
  70 +
  71 + <fieldType name="textTight" class="solr.TextField" positionIncrementGap="100" >
  72 + <analyzer>
  73 + <tokenizer class="solr.WhitespaceTokenizerFactory"/>
  74 + <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="false"/>
  75 + <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
  76 + <filter class="solr.WordDelimiterFilterFactory" generateWordParts="0" generateNumberParts="0" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
  77 + <filter class="solr.LowerCaseFilterFactory"/>
  78 + <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/>
  79 + <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
  80 + </analyzer>
  81 + </fieldType>
  82 +
  83 + <fieldType name="alphaOnlySort" class="solr.TextField" sortMissingLast="true" omitNorms="false">
  84 + <analyzer>
  85 + <tokenizer class="solr.KeywordTokenizerFactory"/>
  86 + <filter class="solr.LowerCaseFilterFactory" />
  87 + <filter class="solr.TrimFilterFactory" />
  88 + <filter class="solr.PatternReplaceFilterFactory" pattern="([^a-z])" replacement="" replace="all"/>
  89 + </analyzer>
  90 + </fieldType>
  91 +
  92 + <fieldtype name="text_zh" class="solr.TextField">
  93 + <analyzer class="org.apache.lucene.analysis.cn.ChineseAnalyzer"/>
  94 + </fieldtype>
  95 +
  96 + </types>
  97 +
  98 +
  99 + <fields>
  100 + <field name="id" type="string" indexed="true" stored="true" required="true" />
  101 + <field name="pk_i" type="integer" indexed="true" stored="true"/>
  102 + <field name="pk_s" type="string" indexed="true" stored="true"/>
  103 + <field name="text" type="text" indexed="true" stored="false" multiValued="true"/>
  104 +
  105 + <dynamicField name="*_i" type="integer" indexed="true" stored="false"/>
  106 + <dynamicField name="*_t" type="text" indexed="true" stored="false"/>
  107 + <dynamicField name="*_f" type="float" indexed="true" stored="false"/>
  108 + <dynamicField name="*_b" type="boolean" indexed="true" stored="false"/>
  109 + <dynamicField name="*_d" type="date" indexed="true" stored="false"/>
  110 + <dynamicField name="*_s" type="string" indexed="true" stored="false"/>
  111 + <dynamicField name="*_ri" type="sint" indexed="true" stored="false"/>
  112 + <dynamicField name="*_rf" type="sfloat" indexed="true" stored="false"/>
  113 + <dynamicField name="*_facet" type="string" indexed="true" stored="false"/>
  114 + <dynamicField name="*_s_mv" type="string" indexed="true" stored="false" multiValued="true"/>
  115 + <dynamicField name="*_zh_text" type="text_zh" indexed="true" stored="false" multiValued="true"/>
  116 + <dynamicField name="*_display" type="text" indexed="false" stored="true" multiValued="true"/>
  117 + </fields>
  118 +
  119 + <uniqueKey>id</uniqueKey>
  120 + <defaultSearchField>text</defaultSearchField>
  121 +
  122 + <copyField source="*_t" dest="text"/>
  123 + <copyField source="*_facet" dest="text"/>
  124 + <solrQueryParser defaultOperator="AND"/>
  125 +
  126 +</schema>
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/scripts.conf 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +# Licensed to the Apache Software Foundation (ASF) under one or more
  2 +# contributor license agreements. See the NOTICE file distributed with
  3 +# this work for additional information regarding copyright ownership.
  4 +# The ASF licenses this file to You under the Apache License, Version 2.0
  5 +# (the "License"); you may not use this file except in compliance with
  6 +# the License. You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +
  16 +user=
  17 +solr_hostname=localhost
  18 +solr_port=8983
  19 +rsyncd_port=18983
  20 +data_dir=
  21 +webapp_name=solr
  22 +master_host=
  23 +master_data_dir=
  24 +master_status_dir=
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/solrconfig.xml 0 → 100644
... ... @@ -0,0 +1,458 @@
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +<!--
  3 + Licensed to the Apache Software Foundation (ASF) under one or more
  4 + contributor license agreements. See the NOTICE file distributed with
  5 + this work for additional information regarding copyright ownership.
  6 + The ASF licenses this file to You under the Apache License, Version 2.0
  7 + (the "License"); you may not use this file except in compliance with
  8 + the License. You may obtain a copy of the License at
  9 +
  10 + http://www.apache.org/licenses/LICENSE-2.0
  11 +
  12 + Unless required by applicable law or agreed to in writing, software
  13 + distributed under the License is distributed on an "AS IS" BASIS,
  14 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15 + See the License for the specific language governing permissions and
  16 + limitations under the License.
  17 +-->
  18 +
  19 +<config>
  20 + <!-- Set this to 'false' if you want solr to continue working after it has
  21 + encountered an severe configuration error. In a production environment,
  22 + you may want solr to keep working even if one handler is mis-configured.
  23 +
  24 + You may also set this to false using by setting the system property:
  25 + -Dsolr.abortOnConfigurationError=false
  26 + -->
  27 + <abortOnConfigurationError>${solr.abortOnConfigurationError:true}</abortOnConfigurationError>
  28 +
  29 + <!-- Used to specify an alternate directory to hold all index data
  30 + other than the default ./data under the Solr home.
  31 + If replication is in use, this should match the replication configuration. -->
  32 +
  33 + <dataDir>${solr.data.dir:./solr/data}</dataDir>
  34 +
  35 + <indexDefaults>
  36 + <!-- Values here affect all index writers and act as a default unless overridden. -->
  37 + <useCompoundFile>false</useCompoundFile>
  38 + <mergeFactor>10</mergeFactor>
  39 + <maxBufferedDocs>1000</maxBufferedDocs>
  40 + <maxMergeDocs>2147483647</maxMergeDocs>
  41 + <maxFieldLength>10000</maxFieldLength>
  42 + <writeLockTimeout>1000</writeLockTimeout>
  43 + <commitLockTimeout>10000</commitLockTimeout>
  44 + </indexDefaults>
  45 +
  46 + <mainIndex>
  47 + <!-- options specific to the main on-disk lucene index -->
  48 + <useCompoundFile>false</useCompoundFile>
  49 + <mergeFactor>10</mergeFactor>
  50 + <maxBufferedDocs>1000</maxBufferedDocs>
  51 + <maxMergeDocs>2147483647</maxMergeDocs>
  52 + <maxFieldLength>10000</maxFieldLength>
  53 +
  54 + <!-- If true, unlock any held write or commit locks on startup.
  55 + This defeats the locking mechanism that allows multiple
  56 + processes to safely access a lucene index, and should be
  57 + used with care. -->
  58 + <unlockOnStartup>false</unlockOnStartup>
  59 + </mainIndex>
  60 +
  61 + <!-- the default high-performance update handler -->
  62 + <updateHandler class="solr.DirectUpdateHandler2">
  63 +
  64 + <!-- A prefix of "solr." for class names is an alias that
  65 + causes solr to search appropriate packages, including
  66 + org.apache.solr.(search|update|request|core|analysis)
  67 + -->
  68 +
  69 + <!-- autocommit pending docs if certain criteria are met
  70 + <autoCommit>
  71 + <maxDocs>10000</maxDocs>
  72 + <maxTime>1000</maxTime>
  73 + </autoCommit>
  74 + -->
  75 +
  76 + <!-- The RunExecutableListener executes an external command.
  77 + exe - the name of the executable to run
  78 + dir - dir to use as the current working directory. default="."
  79 + wait - the calling thread waits until the executable returns. default="true"
  80 + args - the arguments to pass to the program. default=nothing
  81 + env - environment variables to set. default=nothing
  82 + -->
  83 + <!-- A postCommit event is fired after every commit or optimize command
  84 + <listener event="postCommit" class="solr.RunExecutableListener">
  85 + <str name="exe">snapshooter</str>
  86 + <str name="dir">solr/bin</str>
  87 + <bool name="wait">true</bool>
  88 + <arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
  89 + <arr name="env"> <str>MYVAR=val1</str> </arr>
  90 + </listener>
  91 + -->
  92 + <!-- A postOptimize event is fired only after every optimize command, useful
  93 + in conjunction with index distribution to only distribute optimized indicies
  94 + <listener event="postOptimize" class="solr.RunExecutableListener">
  95 + <str name="exe">snapshooter</str>
  96 + <str name="dir">solr/bin</str>
  97 + <bool name="wait">true</bool>
  98 + </listener>
  99 + -->
  100 +
  101 + </updateHandler>
  102 +
  103 +
  104 + <query>
  105 + <!-- Maximum number of clauses in a boolean query... can affect
  106 + range or prefix queries that expand to big boolean
  107 + queries. An exception is thrown if exceeded. -->
  108 + <maxBooleanClauses>1024</maxBooleanClauses>
  109 +
  110 +
  111 + <!-- Cache used by SolrIndexSearcher for filters (DocSets),
  112 + unordered sets of *all* documents that match a query.
  113 + When a new searcher is opened, its caches may be prepopulated
  114 + or "autowarmed" using data from caches in the old searcher.
  115 + autowarmCount is the number of items to prepopulate. For LRUCache,
  116 + the autowarmed items will be the most recently accessed items.
  117 + Parameters:
  118 + class - the SolrCache implementation (currently only LRUCache)
  119 + size - the maximum number of entries in the cache
  120 + initialSize - the initial capacity (number of entries) of
  121 + the cache. (seel java.util.HashMap)
  122 + autowarmCount - the number of entries to prepopulate from
  123 + and old cache.
  124 + -->
  125 + <filterCache
  126 + class="solr.LRUCache"
  127 + size="512"
  128 + initialSize="512"
  129 + autowarmCount="256"/>
  130 +
  131 + <!-- queryResultCache caches results of searches - ordered lists of
  132 + document ids (DocList) based on a query, a sort, and the range
  133 + of documents requested. -->
  134 + <queryResultCache
  135 + class="solr.LRUCache"
  136 + size="512"
  137 + initialSize="512"
  138 + autowarmCount="256"/>
  139 +
  140 + <!-- documentCache caches Lucene Document objects (the stored fields for each document).
  141 + Since Lucene internal document ids are transient, this cache will not be autowarmed. -->
  142 + <documentCache
  143 + class="solr.LRUCache"
  144 + size="512"
  145 + initialSize="512"
  146 + autowarmCount="0"/>
  147 +
  148 + <!-- If true, stored fields that are not requested will be loaded lazily.
  149 +
  150 + This can result in a significant speed improvement if the usual case is to
  151 + not load all stored fields, especially if the skipped fields are large compressed
  152 + text fields.
  153 + -->
  154 + <enableLazyFieldLoading>true</enableLazyFieldLoading>
  155 +
  156 + <!-- Example of a generic cache. These caches may be accessed by name
  157 + through SolrIndexSearcher.getCache(),cacheLookup(), and cacheInsert().
  158 + The purpose is to enable easy caching of user/application level data.
  159 + The regenerator argument should be specified as an implementation
  160 + of solr.search.CacheRegenerator if autowarming is desired. -->
  161 + <!--
  162 + <cache name="myUserCache"
  163 + class="solr.LRUCache"
  164 + size="4096"
  165 + initialSize="1024"
  166 + autowarmCount="1024"
  167 + regenerator="org.mycompany.mypackage.MyRegenerator"
  168 + />
  169 + -->
  170 +
  171 + <!-- An optimization that attempts to use a filter to satisfy a search.
  172 + If the requested sort does not include score, then the filterCache
  173 + will be checked for a filter matching the query. If found, the filter
  174 + will be used as the source of document ids, and then the sort will be
  175 + applied to that.
  176 + <useFilterForSortedQuery>true</useFilterForSortedQuery>
  177 + -->
  178 +
  179 + <!-- An optimization for use with the queryResultCache. When a search
  180 + is requested, a superset of the requested number of document ids
  181 + are collected. For example, if a search for a particular query
  182 + requests matching documents 10 through 19, and queryWindowSize is 50,
  183 + then documents 0 through 50 will be collected and cached. Any further
  184 + requests in that range can be satisfied via the cache. -->
  185 + <queryResultWindowSize>10</queryResultWindowSize>
  186 +
  187 + <!-- This entry enables an int hash representation for filters (DocSets)
  188 + when the number of items in the set is less than maxSize. For smaller
  189 + sets, this representation is more memory efficient, more efficient to
  190 + iterate over, and faster to take intersections. -->
  191 + <HashDocSet maxSize="3000" loadFactor="0.75"/>
  192 +
  193 +
  194 + <!-- boolToFilterOptimizer converts boolean clauses with zero boost
  195 + into cached filters if the number of docs selected by the clause exceeds
  196 + the threshold (represented as a fraction of the total index) -->
  197 + <boolTofilterOptimizer enabled="true" cacheSize="32" threshold=".05"/>
  198 +
  199 +
  200 + <!-- a newSearcher event is fired whenever a new searcher is being prepared
  201 + and there is a current searcher handling requests (aka registered). -->
  202 + <!-- QuerySenderListener takes an array of NamedList and executes a
  203 + local query request for each NamedList in sequence. -->
  204 + <!--
  205 + <listener event="newSearcher" class="solr.QuerySenderListener">
  206 + <arr name="queries">
  207 + <lst> <str name="q">solr</str> <str name="start">0</str> <str name="rows">10</str> </lst>
  208 + <lst> <str name="q">rocks</str> <str name="start">0</str> <str name="rows">10</str> </lst>
  209 + </arr>
  210 + </listener>
  211 + -->
  212 +
  213 + <!-- a firstSearcher event is fired whenever a new searcher is being
  214 + prepared but there is no current registered searcher to handle
  215 + requests or to gain autowarming data from. -->
  216 + <!--
  217 + <listener event="firstSearcher" class="solr.QuerySenderListener">
  218 + <arr name="queries">
  219 + <lst> <str name="q">fast_warm</str> <str name="start">0</str> <str name="rows">10</str> </lst>
  220 + </arr>
  221 + </listener>
  222 + -->
  223 +
  224 + <!-- If a search request comes in and there is no current registered searcher,
  225 + then immediately register the still warming searcher and use it. If
  226 + "false" then all requests will block until the first searcher is done
  227 + warming. -->
  228 + <useColdSearcher>false</useColdSearcher>
  229 +
  230 + <!-- Maximum number of searchers that may be warming in the background
  231 + concurrently. An error is returned if this limit is exceeded. Recommend
  232 + 1-2 for read-only slaves, higher for masters w/o cache warming. -->
  233 + <maxWarmingSearchers>4</maxWarmingSearchers>
  234 +
  235 + </query>
  236 +
  237 + <!--
  238 + Let the dispatch filter handler /select?qt=XXX
  239 + handleSelect=true will use consistent error handling for /select and /update
  240 + handleSelect=false will use solr1.1 style error formatting
  241 + -->
  242 + <requestDispatcher handleSelect="true" >
  243 + <!--Make sure your system has some authentication before enabling remote streaming! -->
  244 + <requestParsers enableRemoteStreaming="false" multipartUploadLimitInKB="2048" />
  245 + </requestDispatcher>
  246 +
  247 +
  248 + <!-- requestHandler plugins... incoming queries will be dispatched to the
  249 + correct handler based on the qt (query type) param matching the
  250 + name of registered handlers.
  251 + The "standard" request handler is the default and will be used if qt
  252 + is not specified in the request.
  253 + -->
  254 + <requestHandler name="standard" class="solr.StandardRequestHandler">
  255 + <!-- default values for query parameters -->
  256 + <lst name="defaults">
  257 + <str name="echoParams">explicit</str>
  258 + <str name="json.nl">map</str>
  259 + <!--
  260 + <int name="rows">10</int>
  261 + <str name="fl">*</str>
  262 + <str name="version">2.1</str>
  263 + -->
  264 + </lst>
  265 + </requestHandler>
  266 +
  267 + <!-- DisMaxRequestHandler allows easy searching across multiple fields
  268 + for simple user-entered phrases.
  269 + see http://wiki.apache.org/solr/DisMaxRequestHandler
  270 + -->
  271 + <requestHandler name="dismax" class="solr.DisMaxRequestHandler" >
  272 + <lst name="defaults">
  273 + <str name="echoParams">explicit</str>
  274 + <float name="tie">0.01</float>
  275 + <str name="qf">
  276 + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
  277 + </str>
  278 + <str name="pf">
  279 + text^0.2 features^1.1 name^1.5 manu^1.4 manu_exact^1.9
  280 + </str>
  281 + <str name="bf">
  282 + ord(poplarity)^0.5 recip(rord(price),1,1000,1000)^0.3
  283 + </str>
  284 + <str name="fl">
  285 + id,name,price,score
  286 + </str>
  287 + <str name="mm">
  288 + 2&lt;-1 5&lt;-2 6&lt;90%
  289 + </str>
  290 + <int name="ps">100</int>
  291 + <str name="q.alt">*:*</str>
  292 + </lst>
  293 + </requestHandler>
  294 +
  295 + <!-- Note how you can register the same handler multiple times with
  296 + different names (and different init parameters)
  297 + -->
  298 + <requestHandler name="partitioned" class="solr.DisMaxRequestHandler" >
  299 + <lst name="defaults">
  300 + <str name="echoParams">explicit</str>
  301 + <str name="qf">text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0</str>
  302 + <str name="mm">2&lt;-1 5&lt;-2 6&lt;90%</str>
  303 + <!-- This is an example of using Date Math to specify a constantly
  304 + moving date range in a config...
  305 + -->
  306 + <str name="bq">incubationdate_dt:[* TO NOW/DAY-1MONTH]^2.2</str>
  307 + </lst>
  308 + <!-- In addition to defaults, "appends" params can be specified
  309 + to identify values which should be appended to the list of
  310 + multi-val params from the query (or the existing "defaults").
  311 +
  312 + In this example, the param "fq=instock:true" will be appended to
  313 + any query time fq params the user may specify, as a mechanism for
  314 + partitioning the index, independent of any user selected filtering
  315 + that may also be desired (perhaps as a result of faceted searching).
  316 +
  317 + NOTE: there is *absolutely* nothing a client can do to prevent these
  318 + "appends" values from being used, so don't use this mechanism
  319 + unless you are sure you always want it.
  320 + -->
  321 + <lst name="appends">
  322 + <str name="fq">inStock:true</str>
  323 + </lst>
  324 + <!-- "invariants" are a way of letting the Solr maintainer lock down
  325 + the options available to Solr clients. Any params values
  326 + specified here are used regardless of what values may be specified
  327 + in either the query, the "defaults", or the "appends" params.
  328 +
  329 + In this example, the facet.field and facet.query params are fixed,
  330 + limiting the facets clients can use. Faceting is not turned on by
  331 + default - but if the client does specify facet=true in the request,
  332 + these are the only facets they will be able to see counts for;
  333 + regardless of what other facet.field or facet.query params they
  334 + may specify.
  335 +
  336 + NOTE: there is *absolutely* nothing a client can do to prevent these
  337 + "invariants" values from being used, so don't use this mechanism
  338 + unless you are sure you always want it.
  339 + -->
  340 + <lst name="invariants">
  341 + <str name="facet.field">cat</str>
  342 + <str name="facet.field">manu_exact</str>
  343 + <str name="facet.query">price:[* TO 500]</str>
  344 + <str name="facet.query">price:[500 TO *]</str>
  345 + </lst>
  346 + </requestHandler>
  347 +
  348 + <requestHandler name="instock" class="solr.DisMaxRequestHandler" >
  349 + <!-- for legacy reasons, DisMaxRequestHandler will assume all init
  350 + params are "defaults" if you don't explicitly specify any defaults.
  351 + -->
  352 + <str name="fq">
  353 + inStock:true
  354 + </str>
  355 + <str name="qf">
  356 + text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
  357 + </str>
  358 + <str name="mm">
  359 + 2&lt;-1 5&lt;-2 6&lt;90%
  360 + </str>
  361 + </requestHandler>
  362 +
  363 +
  364 + <!-- SpellCheckerRequestHandler takes in a word (or several words) as the
  365 + value of the "q" parameter and returns a list of alternative spelling
  366 + suggestions. If invoked with a ...&cmd=rebuild, it will rebuild the
  367 + spellchecker index.
  368 + -->
  369 + <requestHandler name="spellchecker" class="solr.SpellCheckerRequestHandler" startup="lazy">
  370 + <!-- default values for query parameters -->
  371 + <lst name="defaults">
  372 + <int name="suggestionCount">1</int>
  373 + <float name="accuracy">0.5</float>
  374 + </lst>
  375 +
  376 + <!-- Main init params for handler -->
  377 +
  378 + <!-- The directory where your SpellChecker Index should live. -->
  379 + <!-- May be absolute, or relative to the Solr "dataDir" directory. -->
  380 + <!-- If this option is not specified, a RAM directory will be used -->
  381 + <str name="spellcheckerIndexDir">spell</str>
  382 +
  383 + <!-- the field in your schema that you want to be able to build -->
  384 + <!-- your spell index on. This should be a field that uses a very -->
  385 + <!-- simple FieldType without a lot of Analysis (ie: string) -->
  386 + <str name="termSourceField">word</str>
  387 +
  388 + </requestHandler>
  389 +
  390 +
  391 + <!-- Update request handler.
  392 +
  393 + Note: Since solr1.1 requestHandlers requires a valid content type header if posted in
  394 + the body. For example, curl now requires: -H 'Content-type:text/xml; charset=utf-8'
  395 + The response format differs from solr1.1 formatting and returns a standard error code.
  396 +
  397 + To enable solr1.1 behavior, remove the /update handler or change its path
  398 + -->
  399 + <requestHandler name="/update" class="solr.XmlUpdateRequestHandler" />
  400 +
  401 + <!-- CSV update handler, loaded on demand -->
  402 + <requestHandler name="/update/csv" class="solr.CSVRequestHandler" startup="lazy" />
  403 +
  404 +
  405 + <!-- Admin Handlers. TODO? There could be a single handler that loads them all... -->
  406 + <requestHandler name="/admin/luke" class="org.apache.solr.handler.admin.LukeRequestHandler" />
  407 + <requestHandler name="/admin/system" class="org.apache.solr.handler.admin.SystemInfoHandler" />
  408 + <requestHandler name="/admin/plugins" class="org.apache.solr.handler.admin.PluginInfoHandler" />
  409 + <requestHandler name="/admin/threads" class="org.apache.solr.handler.admin.ThreadDumpHandler" />
  410 + <requestHandler name="/admin/properties" class="org.apache.solr.handler.admin.PropertiesRequestHandler" />
  411 +
  412 + <!-- Echo the request contents back to the client -->
  413 + <requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
  414 + <lst name="defaults">
  415 + <str name="echoParams">explicit</str> <!-- for all params (including the default etc) use: 'all' -->
  416 + <str name="echoHandler">true</str>
  417 + </lst>
  418 + </requestHandler>
  419 +
  420 + <!-- queryResponseWriter plugins... query responses will be written using the
  421 + writer specified by the 'wt' request parameter matching the name of a registered
  422 + writer.
  423 + The "standard" writer is the default and will be used if 'wt' is not specified
  424 + in the request. XMLResponseWriter will be used if nothing is specified here.
  425 + The json, python, and ruby writers are also available by default.
  426 +
  427 + <queryResponseWriter name="standard" class="org.apache.solr.request.XMLResponseWriter"/>
  428 + <queryResponseWriter name="json" class="org.apache.solr.request.JSONResponseWriter"/>
  429 + <queryResponseWriter name="python" class="org.apache.solr.request.PythonResponseWriter"/>
  430 + <queryResponseWriter name="ruby" class="org.apache.solr.request.RubyResponseWriter"/>
  431 +
  432 + <queryResponseWriter name="custom" class="com.example.MyResponseWriter"/>
  433 + -->
  434 +
  435 + <!-- XSLT response writer transforms the XML output by any xslt file found
  436 + in Solr's conf/xslt directory. Changes to xslt files are checked for
  437 + every xsltCacheLifetimeSeconds.
  438 + -->
  439 + <queryResponseWriter name="xslt" class="org.apache.solr.request.XSLTResponseWriter">
  440 + <int name="xsltCacheLifetimeSeconds">5</int>
  441 + </queryResponseWriter>
  442 +
  443 + <!-- config for the admin interface -->
  444 + <admin>
  445 + <defaultQuery>solr</defaultQuery>
  446 + <gettableFiles>solrconfig.xml schema.xml admin-extra.html</gettableFiles>
  447 + <!-- pingQuery should be "URLish" ...
  448 + &amp; separated key=val pairs ... but there shouldn't be any
  449 + URL escaping of the values -->
  450 + <pingQuery>
  451 + qt=standard&amp;q=solrpingquery
  452 + </pingQuery>
  453 + <!-- configure a healthcheck file for servers behind a loadbalancer
  454 + <healthcheck type="file">server-enabled</healthcheck>
  455 + -->
  456 + </admin>
  457 +
  458 +</config>
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/stopwords.txt 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +# Licensed to the Apache Software Foundation (ASF) under one or more
  2 +# contributor license agreements. See the NOTICE file distributed with
  3 +# this work for additional information regarding copyright ownership.
  4 +# The ASF licenses this file to You under the Apache License, Version 2.0
  5 +# (the "License"); you may not use this file except in compliance with
  6 +# the License. You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +
  16 +#-----------------------------------------------------------------------
  17 +# a couple of test stopwords to test that the words are really being
  18 +# configured from this file:
  19 +stopworda
  20 +stopwordb
  21 +
  22 +#Standard english stop words taken from Lucene's StopAnalyzer
  23 +an
  24 +and
  25 +are
  26 +as
  27 +at
  28 +be
  29 +but
  30 +by
  31 +for
  32 +if
  33 +in
  34 +into
  35 +is
  36 +it
  37 +no
  38 +not
  39 +of
  40 +on
  41 +or
  42 +s
  43 +such
  44 +t
  45 +that
  46 +the
  47 +their
  48 +then
  49 +there
  50 +these
  51 +they
  52 +this
  53 +to
  54 +was
  55 +will
  56 +with
  57 +
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/synonyms.txt 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +# The ASF licenses this file to You under the Apache License, Version 2.0
  2 +# (the "License"); you may not use this file except in compliance with
  3 +# the License. You may obtain a copy of the License at
  4 +#
  5 +# http://www.apache.org/licenses/LICENSE-2.0
  6 +#
  7 +# Unless required by applicable law or agreed to in writing, software
  8 +# distributed under the License is distributed on an "AS IS" BASIS,
  9 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10 +# See the License for the specific language governing permissions and
  11 +# limitations under the License.
  12 +
  13 +#-----------------------------------------------------------------------
  14 +#some test synonym mappings unlikely to appear in real input text
  15 +aaa => aaaa
  16 +bbb => bbbb1 bbbb2
  17 +ccc => cccc1,cccc2
  18 +a\=>a => b\=>b
  19 +a\,a => b\,b
  20 +fooaaa,baraaa,bazaaa
  21 +
  22 +# Some synonym groups specific to this example
  23 +GB,gib,gigabyte,gigabytes
  24 +MB,mib,megabyte,megabytes
  25 +Television, Televisions, TV, TVs
  26 +#notice we use "gib" instead of "GiB" so any WordDelimiterFilter coming
  27 +#after us won't split it into two words.
  28 +
  29 +# Synonym mappings can be used for spelling correction too
  30 +pixima => pixma
  31 +
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/xslt/example.xsl 0 → 100644
... ... @@ -0,0 +1,132 @@
  1 +<?xml version='1.0' encoding='UTF-8'?>
  2 +
  3 +<!--
  4 + * Licensed to the Apache Software Foundation (ASF) under one or more
  5 + * contributor license agreements. See the NOTICE file distributed with
  6 + * this work for additional information regarding copyright ownership.
  7 + * The ASF licenses this file to You under the Apache License, Version 2.0
  8 + * (the "License"); you may not use this file except in compliance with
  9 + * the License. You may obtain a copy of the License at
  10 + *
  11 + * http://www.apache.org/licenses/LICENSE-2.0
  12 + *
  13 + * Unless required by applicable law or agreed to in writing, software
  14 + * distributed under the License is distributed on an "AS IS" BASIS,
  15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16 + * See the License for the specific language governing permissions and
  17 + * limitations under the License.
  18 + -->
  19 +
  20 +<!--
  21 + Simple transform of Solr query results to HTML
  22 + -->
  23 +<xsl:stylesheet version='1.0'
  24 + xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
  25 +>
  26 +
  27 + <xsl:output media-type="text/html; charset=UTF-8" encoding="UTF-8"/>
  28 +
  29 + <xsl:variable name="title" select="concat('Solr search results (',response/result/@numFound,' documents)')"/>
  30 +
  31 + <xsl:template match='/'>
  32 + <html>
  33 + <head>
  34 + <title><xsl:value-of select="$title"/></title>
  35 + <xsl:call-template name="css"/>
  36 + </head>
  37 + <body>
  38 + <h1><xsl:value-of select="$title"/></h1>
  39 + <div class="note">
  40 + This has been formatted by the sample "example.xsl" transform -
  41 + use your own XSLT to get a nicer page
  42 + </div>
  43 + <xsl:apply-templates select="response/result/doc"/>
  44 + </body>
  45 + </html>
  46 + </xsl:template>
  47 +
  48 + <xsl:template match="doc">
  49 + <xsl:variable name="pos" select="position()"/>
  50 + <div class="doc">
  51 + <table width="100%">
  52 + <xsl:apply-templates>
  53 + <xsl:with-param name="pos"><xsl:value-of select="$pos"/></xsl:with-param>
  54 + </xsl:apply-templates>
  55 + </table>
  56 + </div>
  57 + </xsl:template>
  58 +
  59 + <xsl:template match="doc/*[@name='score']" priority="100">
  60 + <xsl:param name="pos"></xsl:param>
  61 + <tr>
  62 + <td class="name">
  63 + <xsl:value-of select="@name"/>
  64 + </td>
  65 + <td class="value">
  66 + <xsl:value-of select="."/>
  67 +
  68 + <xsl:if test="boolean(//lst[@name='explain'])">
  69 + <xsl:element name="a">
  70 + <!-- can't allow whitespace here -->
  71 + <xsl:attribute name="href">javascript:toggle("<xsl:value-of select="concat('exp-',$pos)" />");</xsl:attribute>?</xsl:element>
  72 + <br/>
  73 + <xsl:element name="div">
  74 + <xsl:attribute name="class">exp</xsl:attribute>
  75 + <xsl:attribute name="id">
  76 + <xsl:value-of select="concat('exp-',$pos)" />
  77 + </xsl:attribute>
  78 + <xsl:value-of select="//lst[@name='explain']/str[position()=$pos]"/>
  79 + </xsl:element>
  80 + </xsl:if>
  81 + </td>
  82 + </tr>
  83 + </xsl:template>
  84 +
  85 + <xsl:template match="doc/arr" priority="100">
  86 + <tr>
  87 + <td class="name">
  88 + <xsl:value-of select="@name"/>
  89 + </td>
  90 + <td class="value">
  91 + <ul>
  92 + <xsl:for-each select="*">
  93 + <li><xsl:value-of select="."/></li>
  94 + </xsl:for-each>
  95 + </ul>
  96 + </td>
  97 + </tr>
  98 + </xsl:template>
  99 +
  100 +
  101 + <xsl:template match="doc/*">
  102 + <tr>
  103 + <td class="name">
  104 + <xsl:value-of select="@name"/>
  105 + </td>
  106 + <td class="value">
  107 + <xsl:value-of select="."/>
  108 + </td>
  109 + </tr>
  110 + </xsl:template>
  111 +
  112 + <xsl:template match="*"/>
  113 +
  114 + <xsl:template name="css">
  115 + <script>
  116 + function toggle(id) {
  117 + var obj = document.getElementById(id);
  118 + obj.style.display = (obj.style.display != 'block') ? 'block' : 'none';
  119 + }
  120 + </script>
  121 + <style type="text/css">
  122 + body { font-family: "Lucida Grande", sans-serif }
  123 + td.name { font-style: italic; font-size:80%; }
  124 + td { vertical-align: top; }
  125 + ul { margin: 0px; margin-left: 1em; padding: 0px; }
  126 + .note { font-size:80%; }
  127 + .doc { margin-top: 1em; border-top: solid grey 1px; }
  128 + .exp { display: none; font-family: monospace; white-space: pre; }
  129 + </style>
  130 + </xsl:template>
  131 +
  132 +</xsl:stylesheet>
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/xslt/example_atom.xsl 0 → 100644
... ... @@ -0,0 +1,63 @@
  1 +<?xml version='1.0' encoding='UTF-8'?>
  2 +
  3 +<!--
  4 + * Licensed to the Apache Software Foundation (ASF) under one or more
  5 + * contributor license agreements. See the NOTICE file distributed with
  6 + * this work for additional information regarding copyright ownership.
  7 + * The ASF licenses this file to You under the Apache License, Version 2.0
  8 + * (the "License"); you may not use this file except in compliance with
  9 + * the License. You may obtain a copy of the License at
  10 + *
  11 + * http://www.apache.org/licenses/LICENSE-2.0
  12 + *
  13 + * Unless required by applicable law or agreed to in writing, software
  14 + * distributed under the License is distributed on an "AS IS" BASIS,
  15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16 + * See the License for the specific language governing permissions and
  17 + * limitations under the License.
  18 + -->
  19 +
  20 +<!--
  21 + Simple transform of Solr query results to Atom
  22 + -->
  23 +
  24 +<xsl:stylesheet version='1.0'
  25 + xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  26 +
  27 + <xsl:output method="xml" encoding="utf-8" />
  28 +
  29 + <xsl:template match='/'>
  30 + <xsl:variable name="query" select="response/lst[@name='responseHeader']/lst[@name='params']/str[@name='q']"/>
  31 + <feed xmlns="http://www.w3.org/2005/Atom">
  32 + <title>Example Solr Atom 1.0 Feed</title>
  33 + <subtitle>
  34 + This has been formatted by the sample "example_atom.xsl" transform -
  35 + use your own XSLT to get a nicer Atom feed.
  36 + </subtitle>
  37 + <author>
  38 + <name>Apache Solr</name>
  39 + <email>solr-user@lucene.apache.org</email>
  40 + </author>
  41 + <link rel="self" type="application/atom+xml"
  42 + href="http://localhost:8983/solr/q={$query}&amp;wt=xslt&amp;tr=atom.xsl"/>
  43 + <updated>
  44 + <xsl:value-of select="response/result/doc[position()=1]/date[@name='timestamp']"/>
  45 + </updated>
  46 + <id>tag:localhost,2007:example</id>
  47 + <xsl:apply-templates select="response/result/doc"/>
  48 + </feed>
  49 + </xsl:template>
  50 +
  51 + <!-- search results xslt -->
  52 + <xsl:template match="doc">
  53 + <xsl:variable name="id" select="str[@name='id']"/>
  54 + <entry>
  55 + <title><xsl:value-of select="str[@name='name']"/></title>
  56 + <link href="http://localhost:8983/solr/select?q={$id}"/>
  57 + <id>tag:localhost,2007:<xsl:value-of select="$id"/></id>
  58 + <summary><xsl:value-of select="arr[@name='features']"/></summary>
  59 + <updated><xsl:value-of select="date[@name='timestamp']"/></updated>
  60 + </entry>
  61 + </xsl:template>
  62 +
  63 +</xsl:stylesheet>
... ...
vendor/plugins/acts_as_solr/solr/solr/conf/xslt/example_rss.xsl 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +<?xml version='1.0' encoding='UTF-8'?>
  2 +
  3 +<!--
  4 + * Licensed to the Apache Software Foundation (ASF) under one or more
  5 + * contributor license agreements. See the NOTICE file distributed with
  6 + * this work for additional information regarding copyright ownership.
  7 + * The ASF licenses this file to You under the Apache License, Version 2.0
  8 + * (the "License"); you may not use this file except in compliance with
  9 + * the License. You may obtain a copy of the License at
  10 + *
  11 + * http://www.apache.org/licenses/LICENSE-2.0
  12 + *
  13 + * Unless required by applicable law or agreed to in writing, software
  14 + * distributed under the License is distributed on an "AS IS" BASIS,
  15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16 + * See the License for the specific language governing permissions and
  17 + * limitations under the License.
  18 + -->
  19 +
  20 +<!--
  21 + Simple transform of Solr query results to RSS
  22 + -->
  23 +
  24 +<xsl:stylesheet version='1.0'
  25 + xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  26 +
  27 + <xsl:output method="xml" encoding="utf-8" />
  28 + <xsl:template match='/'>
  29 + <rss version="2.0">
  30 + <channel>
  31 + <title>Example Solr RSS 2.0 Feed</title>
  32 + <link>http://localhost:8983/solr</link>
  33 + <description>
  34 + This has been formatted by the sample "example_rss.xsl" transform -
  35 + use your own XSLT to get a nicer RSS feed.
  36 + </description>
  37 + <language>en-us</language>
  38 + <docs>http://localhost:8983/solr</docs>
  39 + <xsl:apply-templates select="response/result/doc"/>
  40 + </channel>
  41 + </rss>
  42 + </xsl:template>
  43 +
  44 + <!-- search results xslt -->
  45 + <xsl:template match="doc">
  46 + <xsl:variable name="id" select="str[@name='id']"/>
  47 + <xsl:variable name="timestamp" select="date[@name='timestamp']"/>
  48 + <item>
  49 + <title><xsl:value-of select="str[@name='name']"/></title>
  50 + <link>
  51 + http://localhost:8983/solr/select?q=id:<xsl:value-of select="$id"/>
  52 + </link>
  53 + <description>
  54 + <xsl:value-of select="arr[@name='features']"/>
  55 + </description>
  56 + <pubDate><xsl:value-of select="$timestamp"/></pubDate>
  57 + <guid>
  58 + http://localhost:8983/solr/select?q=id:<xsl:value-of select="$id"/>
  59 + </guid>
  60 + </item>
  61 + </xsl:template>
  62 +</xsl:stylesheet>
... ...
vendor/plugins/acts_as_solr/test/config/solr.yml 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +test:
  2 + url: http://localhost:8981/solr
0 3 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/test/db/connections/mysql/connection.rb 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +require 'logger'
  2 +ActiveRecord::Base.logger = Logger.new("debug.log")
  3 +
  4 +ActiveRecord::Base.establish_connection(
  5 + :adapter => "mysql",
  6 + :username => MYSQL_USER,
  7 + :encoding => "utf8",
  8 + :database => "actsassolr_tests"
  9 +)
  10 +
... ...
vendor/plugins/acts_as_solr/test/db/connections/sqlite/connection.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +require 'logger'
  2 +ActiveRecord::Base.logger = Logger.new("debug.log")
  3 +
  4 +ActiveRecord::Base.establish_connection(
  5 + :adapter => "sqlite3",
  6 + :encoding => "utf8",
  7 + :database => File.join(File.dirname(File.expand_path(__FILE__)), '..', '..', 'test.db')
  8 +)
... ...
vendor/plugins/acts_as_solr/test/db/migrate/001_create_books.rb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +class CreateBooks < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :books, :force => true do |t|
  4 + t.column :category_id, :integer
  5 + t.column :name, :string
  6 + t.column :author, :string
  7 + t.column :type, :string
  8 + t.column :published_on, :date
  9 + end
  10 + end
  11 +
  12 + def self.down
  13 + drop_table :books
  14 + end
  15 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/002_create_movies.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class CreateMovies < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :movies, :force => true do |t|
  4 + t.column :name, :string
  5 + t.column :description, :string
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + drop_table :movies
  11 + end
  12 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/003_create_categories.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class CreateCategories < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :categories, :force => true do |t|
  4 + t.column :name, :string
  5 + end
  6 + end
  7 +
  8 + def self.down
  9 + drop_table :categories
  10 + end
  11 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/004_create_electronics.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +class CreateElectronics < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :electronics, :force => true do |t|
  4 + t.column :name, :string
  5 + t.column :manufacturer, :string
  6 + t.column :features, :string
  7 + t.column :category, :string
  8 + t.column :price, :string
  9 + t.timestamps
  10 + end
  11 + end
  12 +
  13 + def self.down
  14 + drop_table :electronics
  15 + end
  16 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/005_create_authors.rb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +class CreateAuthors < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :authors, :force => true do |t|
  4 + t.column :name, :string
  5 + t.column :biography, :text
  6 + end
  7 + end
  8 +
  9 + def self.down
  10 + drop_table :authors
  11 + end
  12 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/006_create_postings.rb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +class CreatePostings < ActiveRecord::Migration
  2 + def self.up
  3 + execute "CREATE TABLE postings(`guid` varchar(20) NOT NULL PRIMARY KEY, `name` varchar(200), `description` text)"
  4 + end
  5 +
  6 + def self.down
  7 + drop_table :postings
  8 + end
  9 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/007_create_posts.rb 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +class CreatePosts < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :posts, :force => true do |t|
  4 + t.column :name, :string
  5 + t.column :reply_counter, :integer
  6 + t.column :posted_at, :datetime
  7 + end
  8 + end
  9 +
  10 + def self.down
  11 + drop_table :posts
  12 + end
  13 +end
... ...
vendor/plugins/acts_as_solr/test/db/migrate/008_create_gadgets.rb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +class CreateGadgets < ActiveRecord::Migration
  2 + def self.up
  3 + create_table :gadgets, :force => true do |t|
  4 + t.column :name, :string
  5 + end
  6 + end
  7 +
  8 + def self.down
  9 + drop_table :gadgets
  10 + end
  11 +end
... ...
vendor/plugins/acts_as_solr/test/fixtures/authors.yml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +tom_clancy:
  2 + id: 1
  3 + name: Tom Clancy
  4 + biography: Tom Clancy (born 1947) writes novels of adventure and espionage in the international military-industrial complex that have earned him enormous popularity in the 1980s as a creator of the "techno-thriller" genre.
  5 +
  6 +stephen_king:
  7 + id: 2
  8 + name: Stephen King
  9 + biography: Stephen King (born 1947) is a prolific and immensely popular author of horror fiction. In his works, King blends elements of the traditional gothic tale with those of the modern psychological thriller, detective, and science fiction genres.
... ...
vendor/plugins/acts_as_solr/test/fixtures/books.yml 0 → 100644
... ... @@ -0,0 +1,13 @@
  1 +splinter_cell:
  2 + id: 1
  3 + category_id: 1
  4 + name: Splinter Cell
  5 + author: Tom Clancy
  6 + published_on: <%= Date.today - 1.year %>
  7 +
  8 +ruby:
  9 + id: 2
  10 + category_id: 2
  11 + name: Ruby for Dummies
  12 + author: Peter McPeterson
  13 + published_on: <%= Date.today - 2.years %>
0 14 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/test/fixtures/categories.yml 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +thriller:
  2 + id: 1
  3 + name: Thriller/Novels
  4 +
  5 +technical:
  6 + id: 2
  7 + name: Technical Books
0 8 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/test/fixtures/db_definitions/mysql.sql 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +DROP DATABASE IF EXISTS `actsassolr_tests`;
  2 +CREATE DATABASE IF NOT EXISTS `actsassolr_tests`;
  3 +USE `actsassolr_tests`
  4 +
  5 +CREATE TABLE `books` (
  6 + `id` int(11) NOT NULL auto_increment,
  7 + `category_id` int(11),
  8 + `name` varchar(200) default NULL,
  9 + `author` varchar(200) default NULL,
  10 + PRIMARY KEY (`id`)
  11 +);
  12 +
  13 +CREATE TABLE `movies` (
  14 + `id` int(11) NOT NULL auto_increment,
  15 + `name` varchar(200) default NULL,
  16 + `description` varchar(255) default NULL,
  17 + PRIMARY KEY (`id`)
  18 +);
  19 +
  20 +CREATE TABLE `categories` (
  21 + `id` int(11) NOT NULL auto_increment,
  22 + `name` varchar(200) default NULL,
  23 + PRIMARY KEY (`id`)
  24 +);
  25 +
  26 +CREATE TABLE `electronics` (
  27 + `id` int(11) NOT NULL auto_increment,
  28 + `name` varchar(200) default NULL,
  29 + `manufacturer` varchar(255) default NULL,
  30 + `features` varchar(255) default NULL,
  31 + `category` varchar(255) default NULL,
  32 + `price` varchar(20) default NULL,
  33 + PRIMARY KEY (`id`)
  34 +);
  35 +
  36 +CREATE TABLE `authors` (
  37 + `id` int(11) NOT NULL auto_increment,
  38 + `name` varchar(200) default NULL,
  39 + `biography` text default NULL,
  40 + PRIMARY KEY (`id`)
  41 +);
... ...
vendor/plugins/acts_as_solr/test/fixtures/electronics.yml 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +ipod_video:
  2 + id: 1
  3 + name: Apple 60 GB Memory iPod with Video Playback Black
  4 + manufacturer: Apple Computer Inc.
  5 + features: iTunes, Podcasts, Audiobooks
  6 + category: Electronics
  7 + price: 599.00
  8 + created_at: <%= Date.today - 1.year %>
  9 + updated_at: <%= Date.today - 1.month %>
  10 +
  11 +dell_monitor:
  12 + id: 2
  13 + name: Dell Widescreen UltraSharp 3007WFP
  14 + manufacturer: Dell, Inc
  15 + features: 30" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast
  16 + category: Electronics
  17 + price: 750.00
  18 + created_at: <%= Date.today - 1.year %>
  19 + updated_at: <%= Date.today - 1.month %>
  20 +
  21 +samsung_hd:
  22 + id: 3
  23 + name: Samsung SpinPoint P120 SP2514N - hard drive - 250 GB of Memory Storage - ATA-133
  24 + manufacturer: Samsung Electronics Co. Ltd.
  25 + features: 7200RPM, 8MB cache, IDE Ultra ATA-133
  26 + category: Hard Drive
  27 + price: 319.00
  28 + created_at: <%= Date.today - 2.years %>
  29 + updated_at: <%= Date.today - 2.months %>
  30 +
  31 +corsair_ram:
  32 + id: 4
  33 + name: CORSAIR XMS 2GB (2 x 1GB) 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) Dual Channel Kit System Memory - Retail
  34 + manufacturer: Corsair Microsystems Inc.
  35 + features: CAS latency 2, 2-3-3-6 timing, 2.75v, unbuffered, heat-spreader
  36 + category: Memory
  37 + price: 155.00
  38 + created_at: <%= Date.today - 6.years %>
  39 + updated_at: <%= Date.today - 3.months %>
  40 +
  41 +a_data_ram:
  42 + id: 5
  43 + name: A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM
  44 + manufacturer: A-DATA Technology Inc.
  45 + features: CAS latency 3, 2.7v
  46 + category: Memory
  47 + price: 65.79
  48 + created_at: <%= Date.today - 9.years %>
  49 + updated_at: <%= Date.today - 4.months %>
... ...
vendor/plugins/acts_as_solr/test/fixtures/movies.yml 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +napoleon_dynamite:
  2 + id: 1
  3 + name: Napoleon Dynamite
  4 + description: Cool movie about a goofy guy
  5 +
  6 +office_space:
  7 + id: 2
  8 + name: Office Space
  9 + description: Hypnotized dude loves fishing but not working
0 10 \ No newline at end of file
... ...
vendor/plugins/acts_as_solr/test/fixtures/postings.yml 0 → 100644
... ... @@ -0,0 +1,10 @@
  1 +first:
  2 + guid: ABC-123
  3 + name: Posting ABC
  4 + description: First posting testing primary key as string
  5 +
  6 +second:
  7 + guid: DEF-456
  8 + name: Posting DEF
  9 + description: Second posting testing primary key as string
  10 +
... ...
vendor/plugins/acts_as_solr/test/functional/acts_as_solr_test.rb 0 → 100644
... ... @@ -0,0 +1,413 @@
  1 +# encoding: utf-8
  2 +require "#{File.dirname(File.expand_path(__FILE__))}/../test_helper"
  3 +
  4 +class ActsAsSolrTest < Test::Unit::TestCase
  5 +
  6 + fixtures :books, :movies, :electronics, :postings, :authors
  7 +
  8 + # Inserting new data into Solr and making sure it's getting indexed
  9 + def test_insert_new_data
  10 + assert_equal 2, Book.count_by_solr('ruby OR splinter OR bob')
  11 + b = Book.create(:name => "Fuze in action", :author => "Bob Bobber", :category_id => 1)
  12 + assert b.valid?
  13 + assert_equal 3, Book.count_by_solr('ruby OR splinter OR bob')
  14 + end
  15 +
  16 + # Check the type column stored in the index isn't stemmed by SOLR. If it is stemmed,
  17 + # then both Post and Posting will be stored as type:Post, so a query for Posts will
  18 + # return Postings and vice versa
  19 +
  20 + def test_insert_new_data_doesnt_stem_type
  21 + assert_equal 0, Post.count_by_solr('aardvark')
  22 + p = Posting.new :name => 'aardvark', :description => "An interesting animal"
  23 + p.guid = '12AB'
  24 + p.save!
  25 + assert_equal 0, Post.count_by_solr('aardvark')
  26 + end
  27 +
  28 + def test_type_determined_from_database_if_not_explicitly_set
  29 + assert Post.configuration[:solr_fields][:posted_at][:type] == :date
  30 + end
  31 +
  32 + def test_search_includes_subclasses
  33 + Novel.create! :name => 'Wuthering Heights', :author => 'Emily Bronte'
  34 + Book.create! :name => 'Jane Eyre', :author => 'Charlotte Bronte'
  35 + assert_equal 1, Novel.find_by_solr('Bronte').total_hits
  36 + assert_equal 2, Book.find_by_solr('Bronte').total_hits
  37 + end
  38 +
  39 + # Testing basic solr search:
  40 + # Model.find_by_solr 'term'
  41 + # Note that you're able to mix free-search with fields and boolean operators
  42 + def test_find_by_solr_ruby
  43 + ['ruby', 'dummy', 'name:ruby', 'name:dummy', 'name:ruby AND author:peter',
  44 + 'author:peter AND ruby', 'peter dummy'].each do |term|
  45 + records = Book.find_by_solr term
  46 + assert_equal 1, records.total
  47 + assert_equal "Peter McPeterson", records.docs.first.author
  48 + assert_equal "Ruby for Dummies", records.docs.first.name
  49 + assert_equal ({"id" => 2,
  50 + "category_id" => 2,
  51 + "name" => "Ruby for Dummies",
  52 + "author" => "Peter McPeterson", "published_on" => (Date.today - 2.years), "type" => nil}), records.docs.first.attributes
  53 + end
  54 + end
  55 +
  56 + # Testing basic solr search:
  57 + # Model.find_by_solr 'term'
  58 + # Note that you're able to mix free-search with fields and boolean operators
  59 + def test_find_by_solr_splinter
  60 + ['splinter', 'name:splinter', 'name:splinter AND author:clancy',
  61 + 'author:clancy AND splinter', 'cell tom'].each do |term|
  62 + records = Book.find_by_solr term
  63 + assert_equal 1, records.total
  64 + assert_equal "Splinter Cell", records.docs.first.name
  65 + assert_equal "Tom Clancy", records.docs.first.author
  66 + assert_equal ({"id" => 1, "category_id" => 1, "name" => "Splinter Cell",
  67 + "author" => "Tom Clancy", "published_on" => (Date.today - 1.year), "type" => nil}), records.docs.first.attributes
  68 + end
  69 + end
  70 +
  71 + # Testing basic solr search:
  72 + # Model.find_by_solr 'term'
  73 + # Note that you're able to mix free-search with fields and boolean operators
  74 + def test_find_by_solr_ruby_or_splinter
  75 + ['ruby OR splinter', 'ruby OR author:tom', 'name:cell OR author:peter', 'dummy OR cell'].each do |term|
  76 + records = Book.find_by_solr term
  77 + assert_equal 2, records.total
  78 + end
  79 + end
  80 +
  81 + # Testing search in indexed field methods:
  82 + #
  83 + # class Movie < ActiveRecord::Base
  84 + # acts_as_solr :fields => [:name, :description, :current_time]
  85 + #
  86 + # def current_time
  87 + # Time.now.to_s
  88 + # end
  89 + #
  90 + # end
  91 + #
  92 + # The method current_time above gets indexed as being part of the
  93 + # Movie model and it's available for search as well
  94 + def test_find_with_dynamic_fields
  95 + date = Time.now.strftime('%b %d %Y')
  96 + ["dynamite AND #{date}", "description:goofy AND #{date}", "goofy napoleon #{date}",
  97 + "goofiness #{date}"].each do |term|
  98 + records = Movie.find_by_solr term
  99 + assert_equal 1, records.total
  100 + assert_equal ({"id" => 1, "name" => "Napoleon Dynamite",
  101 + "description" => "Cool movie about a goofy guy"}), records.docs.first.attributes
  102 + end
  103 + end
  104 +
  105 + # Testing basic solr search that returns just the ids instead of the objects:
  106 + # Model.find_id_by_solr 'term'
  107 + # Note that you're able to mix free-search with fields and boolean operators
  108 + def test_find_id_by_solr_ruby
  109 + ['ruby', 'dummy', 'name:ruby', 'name:dummy', 'name:ruby AND author:peter',
  110 + 'author:peter AND ruby'].each do |term|
  111 + records = Book.find_id_by_solr term
  112 + assert_equal 1, records.docs.size
  113 + assert_equal [2], records.docs
  114 + end
  115 + end
  116 +
  117 + # Testing basic solr search that returns just the ids instead of the objects:
  118 + # Model.find_id_by_solr 'term'
  119 + # Note that you're able to mix free-search with fields and boolean operators
  120 + def test_find_id_by_solr_splinter
  121 + ['splinter', 'name:splinter', 'name:splinter AND author:clancy',
  122 + 'author:clancy AND splinter'].each do |term|
  123 + records = Book.find_id_by_solr term
  124 + assert_equal 1, records.docs.size
  125 + assert_equal [1], records.docs
  126 + end
  127 + end
  128 +
  129 + # Testing basic solr search that returns just the ids instead of the objects:
  130 + # Model.find_id_by_solr 'term'
  131 + # Note that you're able to mix free-search with fields and boolean operators
  132 + def test_find_id_by_solr_ruby_or_splinter
  133 + ['ruby OR splinter', 'ruby OR author:tom', 'name:cell OR author:peter',
  134 + 'dummy OR cell'].each do |term|
  135 + records = Book.find_id_by_solr term
  136 + assert_equal 2, records.docs.size
  137 + assert_equal [1,2], records.docs
  138 + end
  139 + end
  140 +
  141 + # Testing basic solr search that returns the total number of records found:
  142 + # Model.find_count_by_solr 'term'
  143 + # Note that you're able to mix free-search with fields and boolean operators
  144 + def test_count_by_solr
  145 + ['ruby', 'dummy', 'name:ruby', 'name:dummy', 'name:ruby AND author:peter',
  146 + 'author:peter AND ruby'].each do |term|
  147 + assert_equal 1, Book.count_by_solr(term), "there should only be 1 result for search: #{term}"
  148 + end
  149 + end
  150 +
  151 + # Testing basic solr search that returns the total number of records found:
  152 + # Model.find_count_by_solr 'term'
  153 + # Note that you're able to mix free-search with fields and boolean operators
  154 + def test_count_by_solr_splinter
  155 + ['splinter', 'name:splinter', 'name:splinter AND author:clancy',
  156 + 'author:clancy AND splinter', 'author:clancy cell'].each do |term|
  157 + assert_equal 1, Book.count_by_solr(term)
  158 + end
  159 + end
  160 +
  161 + # Testing basic solr search that returns the total number of records found:
  162 + # Model.find_count_by_solr 'term'
  163 + # Note that you're able to mix free-search with fields and boolean operators
  164 + def test_count_by_solr_ruby_or_splinter
  165 + ['ruby OR splinter', 'ruby OR author:tom', 'name:cell OR author:peter', 'dummy OR cell'].each do |term|
  166 + assert_equal 2, Book.count_by_solr(term)
  167 + end
  168 + end
  169 +
  170 + # Testing basic solr search with additional options:
  171 + # Model.find_count_by_solr 'term', :limit => 10, :offset => 0
  172 + def test_find_with_options
  173 + [1,2].each do |count|
  174 + records = Book.find_by_solr 'ruby OR splinter', :limit => count
  175 + assert_equal count, records.docs.size
  176 + end
  177 + end
  178 +
  179 + # Testing self.rebuild_solr_index
  180 + # - It makes sure the index is rebuilt after a data has been lost
  181 + def test_rebuild_solr_index
  182 + assert_equal 1, Book.count_by_solr('splinter')
  183 +
  184 + Book.find(:first).solr_destroy
  185 + assert_equal 0, Book.count_by_solr('splinter')
  186 +
  187 + Book.rebuild_solr_index
  188 + assert_equal 1, Book.count_by_solr('splinter')
  189 + end
  190 +
  191 + # Testing instance methods:
  192 + # - solr_save
  193 + # - solr_destroy
  194 + def test_solr_save_and_solr_destroy
  195 + assert_equal 1, Book.count_by_solr('splinter')
  196 +
  197 + Book.find(:first).solr_destroy
  198 + assert_equal 0, Book.count_by_solr('splinter')
  199 +
  200 + Book.find(:first).solr_save
  201 + assert_equal 1, Book.count_by_solr('splinter')
  202 + end
  203 +
  204 + # Testing the order of results
  205 + def test_find_returns_records_in_order
  206 + records = Book.find_by_solr 'ruby^5 OR splinter'
  207 + # we boosted ruby so ruby should come first
  208 +
  209 + assert_equal 2, records.total
  210 + assert_equal 'Ruby for Dummies', records.docs.first.name
  211 + assert_equal 'Splinter Cell', records.docs.last.name
  212 + end
  213 +
  214 + # Testing solr search with optional :order argument
  215 + def _test_with_order_option
  216 + records = Movie.find_by_solr 'office^5 OR goofiness'
  217 + assert_equal 'Hypnotized dude loves fishing but not working', records.docs.first.description
  218 + assert_equal 'Cool movie about a goofy guy', records.docs.last.description
  219 +
  220 + records = Movie.find_by_solr 'office^5 OR goofiness', :order => 'description asc'
  221 + assert_equal 'Cool movie about a goofy guy', records.docs.first.description
  222 + assert_equal 'Hypnotized dude loves fishing but not working', records.docs.last.description
  223 + end
  224 +
  225 + # Testing search with omitted :field_types should
  226 + # return the same result set as if when we use it
  227 + def test_omit_field_types_in_search
  228 + records = Electronic.find_by_solr "price:[200 TO 599.99]"
  229 + assert_match(/599/, records.docs.first.price)
  230 + assert_match(/319/, records.docs.last.price)
  231 +
  232 + records = Electronic.find_by_solr "price:[200 TO 599.99]", :order => 'price asc'
  233 + assert_match(/319/, records.docs.first.price)
  234 + assert_match(/599/, records.docs.last.price)
  235 +
  236 + end
  237 +
  238 + # Test to make sure the result returned when no matches
  239 + # are found has the same structure when there are results
  240 + def test_returns_no_matches
  241 + records = Book.find_by_solr 'rubyist'
  242 + assert_equal [], records.docs
  243 + assert_equal 0, records.total
  244 +
  245 + records = Book.find_id_by_solr 'rubyist'
  246 + assert_equal [], records.docs
  247 + assert_equal 0, records.total
  248 +
  249 + records = Book.find_by_solr 'rubyist', :facets => {}
  250 + assert_equal [], records.docs
  251 + assert_equal 0, records.total
  252 + assert_equal({"facet_fields"=>[]}, records.facets)
  253 + end
  254 +
  255 +
  256 + # Testing the :exclude_fields option when set in the
  257 + # model to make sure it doesn't get indexed
  258 + def test_exclude_fields_option
  259 + records = Electronic.find_by_solr 'audiobooks OR latency'
  260 + assert records.docs.empty?
  261 + assert_equal 0, records.total
  262 +
  263 + assert_nothing_raised{
  264 + records = Electronic.find_by_solr 'features:audiobooks'
  265 + assert records.docs.empty?
  266 + assert_equal 0, records.total
  267 + }
  268 + end
  269 +
  270 + # Testing the :auto_commit option set to false in the model
  271 + # should not send the commit command to Solr
  272 + def test_auto_commit_turned_off
  273 + assert_equal 0, Author.count_by_solr('raymond chandler')
  274 +
  275 + original_count = Author.count
  276 + Author.create(:name => 'Raymond Chandler', :biography => 'Writes noirish detective stories')
  277 +
  278 + assert_equal original_count + 1, Author.count
  279 + assert_equal 0, Author.count_by_solr('raymond chandler')
  280 + end
  281 +
  282 + # Testing models that use a different key as the primary key
  283 + def test_search_on_model_with_string_id_field
  284 + records = Posting.find_by_solr 'first^5 OR second'
  285 + assert_equal 2, records.total
  286 + assert_equal 'ABC-123', records.docs.first.guid
  287 + assert_equal 'DEF-456', records.docs.last.guid
  288 + end
  289 +
  290 + # Making sure the result set is ordered correctly even on
  291 + # models that use a different key as the primary key
  292 + def test_records_in_order_on_model_with_string_id_field
  293 + records = Posting.find_by_solr 'first OR second^5'
  294 + assert_equal 2, records.total
  295 + assert_equal 'DEF-456', records.docs.first.guid
  296 + assert_equal 'ABC-123', records.docs.last.guid
  297 + end
  298 +
  299 + # Making sure the records are added when passing a batch size
  300 + # to rebuild_solr_index
  301 + def test_using_rebuild_solr_index_with_batch
  302 + assert_equal 2, Movie.count_by_solr('office OR napoleon')
  303 + Movie.find(:all).each(&:solr_destroy)
  304 + assert_equal 0, Movie.count_by_solr('office OR napoleon')
  305 +
  306 + Movie.rebuild_solr_index 100
  307 + assert_equal 2, Movie.count_by_solr('office OR napoleon')
  308 + end
  309 +
  310 + # Making sure find_by_solr with scores actually return the scores
  311 + # for each individual record
  312 + def test_find_by_solr_with_score
  313 + books = Book.find_by_solr 'ruby^10 OR splinter', :scores => true
  314 + assert_equal 2, books.total
  315 + assert_equal 0.41763234, books.max_score
  316 +
  317 + books.records.each { |book| assert_not_nil book.solr_score }
  318 + assert_equal 0.41763234, books.docs.first.solr_score
  319 + assert_equal 0.14354616, books.docs.last.solr_score
  320 + end
  321 +
  322 + # Making sure nothing breaks when html entities are inside
  323 + # the content to be indexed; and on the search as well.
  324 + def test_index_and_search_with_html_entities
  325 + description = "
  326 + inverted exclamation mark &iexcl; &#161;
  327 + ¤ currency &curren; &#164;
  328 + ¢ cent &cent; &#162;
  329 + £ pound &pound; &#163;
  330 + ¥ yen &yen; &#165;
  331 + ¦ broken vertical bar &brvbar; &#166;
  332 + § section &sect; &#167;
  333 + ¨ spacing diaeresis &uml; &#168;
  334 + © copyright &copy; &#169;
  335 + ª feminine ordinal indicator &ordf; &#170;
  336 + « angle quotation mark (left) &laquo; &#171;
  337 + ¬ negation &not; &#172;
  338 + ­ soft hyphen &shy; &#173;
  339 + ® registered trademark &reg; &#174;
  340 + ™ trademark &trade; &#8482;
  341 + ¯ spacing macron &macr; &#175;
  342 + ° degree &deg; &#176;
  343 + ± plus-or-minus &plusmn; &#177;
  344 + ² superscript 2 &sup2; &#178;
  345 + ³ superscript 3 &sup3; &#179;
  346 + ´ spacing acute &acute; &#180;
  347 + µ micro &micro; &#181;
  348 + ¶ paragraph &para; &#182;
  349 + · middle dot &middot; &#183;
  350 + ¸ spacing cedilla &cedil; &#184;
  351 + ¹ superscript 1 &sup1; &#185;
  352 + º masculine ordinal indicator &ordm; &#186;
  353 + » angle quotation mark (right) &raquo; &#187;
  354 + ¼ fraction 1/4 &frac14; &#188;
  355 + ½ fraction 1/2 &frac12; &#189;
  356 + ¾ fraction 3/4 &frac34; &#190;
  357 + ¿ inverted question mark &iquest; &#191;
  358 + × multiplication &times; &#215;
  359 + ÷ division &divide; &#247
  360 + &hearts; &diams; &clubs; &spades;"
  361 +
  362 + author = Author.create(:name => "Test in Action&trade; - Copyright &copy; Bob", :biography => description)
  363 + assert author.valid?
  364 + author.solr_commit
  365 +
  366 + author = Author.find_by_solr 'trademark &copy &#190 &iexcl &#163'
  367 + assert_equal 1, author.total
  368 + end
  369 +
  370 + def test_operator_search_option
  371 + assert_nothing_raised {
  372 + books = Movie.find_by_solr "office napoleon", :operator => :or
  373 + assert_equal 2, books.total
  374 +
  375 + books = Movie.find_by_solr "office napoleon", :operator => :and
  376 + assert_equal 0, books.total
  377 + }
  378 +
  379 + assert_raise RuntimeError do
  380 + Movie.find_by_solr "office napoleon", :operator => :bad
  381 + end
  382 + end
  383 +
  384 + # Making sure find_by_solr with scores actually return the scores
  385 + # for each individual record and orders them accordingly
  386 + def test_find_by_solr_order_by_score
  387 + books = Book.find_by_solr 'ruby^10 OR splinter', {:scores => true, :order => 'score asc' }
  388 + assert (books.docs.collect(&:solr_score).compact.size == books.docs.size), "Each book should have a score"
  389 + assert_equal 0.41763234, books.docs.last.solr_score
  390 +
  391 + books = Book.find_by_solr 'ruby^10 OR splinter', {:scores => true, :order => 'score desc' }
  392 + assert_equal 0.41763234, books.docs.first.solr_score
  393 + assert_equal 0.14354616, books.docs.last.solr_score
  394 + end
  395 +
  396 + # Search based on fields with the :date format
  397 + def test_indexed_date_field_format
  398 + movies = Movie.find_by_solr 'time_on_xml:[NOW-1DAY TO NOW]'
  399 + assert_equal 2, movies.total
  400 + end
  401 +
  402 + def test_query_time_is_returned
  403 + results = Book.find_by_solr('ruby')
  404 + assert_not_nil(results.query_time)
  405 + assert_equal(results.query_time.class,Fixnum)
  406 + end
  407 +
  408 + def test_should_not_index_the_record_when_offline_proc_returns_true
  409 + Gadget.search_disabled = true
  410 + gadget = Gadget.create(:name => "flipvideo mino")
  411 + assert_equal 0, Gadget.find_id_by_solr('flipvideo').total
  412 + end
  413 +end
... ...
vendor/plugins/acts_as_solr/test/functional/association_indexing_test.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +require File.join(File.dirname(__FILE__), '../test_helper')
  2 +
  3 +class AssociationIndexingTest < Test::Unit::TestCase
  4 +
  5 + fixtures :categories, :books
  6 +
  7 + # Testing the association indexing with has_many:
  8 + #
  9 + # class Category < ActiveRecord::Base
  10 + # has_many :books
  11 + # acts_as_solr :include => [:books]
  12 + # end
  13 + #
  14 + # Note that some of the search terms below are from the 'books'
  15 + # table, but get indexed as being a part of Category
  16 + def test_search_on_fields_in_has_many_association
  17 + ['thriller', 'novel', 'splinter', 'clancy', 'tom clancy thriller'].each do |term|
  18 + assert_equal 1, Category.count_by_solr(term), "expected one result: #{term}"
  19 + end
  20 + end
  21 +
  22 + # Testing the association indexing with belongs_to:
  23 + #
  24 + # class Book < ActiveRecord::Base
  25 + # belongs_to :category
  26 + # acts_as_solr :include => [:category]
  27 + # end
  28 + #
  29 + # Note that some of the search terms below are from the 'categories'
  30 + # table, but get indexed as being a part of Book
  31 + def test_search_on_fields_in_belongs_to_association
  32 + ['splinter', 'clancy', 'tom clancy thriller', 'splinter novel'].each do |term|
  33 + assert_equal 1, Book.count_by_solr(term), "expected one result: #{term}"
  34 + end
  35 + end
  36 +
  37 +end
... ...
vendor/plugins/acts_as_solr/test/functional/faceted_search_test.rb 0 → 100644
... ... @@ -0,0 +1,163 @@
  1 +require File.join(File.dirname(__FILE__), '../test_helper')
  2 +
  3 +class FacetedSearchTest < Test::Unit::TestCase
  4 +
  5 + fixtures :electronics
  6 +
  7 + # The tests below are for faceted search, but make sure you setup
  8 + # the fields on your model you'd like to index as a facet field:
  9 + #
  10 + # class Electronic < ActiveRecord::Base
  11 + # acts_as_solr :facets => [:category, :manufacturer]
  12 + # end
  13 + #
  14 + # A basic faceted search using just one facet field
  15 + def test_faceted_search_basic
  16 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:category]}
  17 + assert_equal 4, records.docs.size
  18 + assert_match /Apple 60 GB Memory iPod/, records.docs.first.name
  19 + assert_equal({"category_facet" => {"Electronics" => 1,
  20 + "Memory" => 2,
  21 + "Hard Drive" => 1}},
  22 + records.facets['facet_fields'])
  23 + end
  24 +
  25 + # Making sure the empty result returned what we expected
  26 + def test_faceted_search_no_matches
  27 + records = Electronic.find_by_solr "not found", :facets => { :fields => [:category]}
  28 + assert_equal [], records.docs
  29 + assert_equal [], records.facets['facet_fields']
  30 + end
  31 +
  32 + # A basic faceted search using multiple facet fields
  33 + def test_faceted_search_multiple_fields
  34 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:category, :manufacturer]}
  35 + assert_equal 4, records.docs.size
  36 + assert_equal({"category_facet" => {"Electronics" => 1,
  37 + "Memory" => 2,
  38 + "Hard Drive" => 1},
  39 + "manufacturer_facet" => {"Dell, Inc" => 0,
  40 + "Samsung Electronics Co. Ltd." => 1,
  41 + "Corsair Microsystems Inc." => 1,
  42 + "A-DATA Technology Inc." => 1,
  43 + "Apple Computer Inc." => 1}}, records.facets['facet_fields'])
  44 + end
  45 +
  46 + # A basic faceted search using facet queries to get counts.
  47 + # Here are the facets search query meaning:
  48 + # "price:[* TO 200]" - Price up to 200
  49 + # "price:[200 TO 500]" - Price from 200 to 500
  50 + # "price:[500 TO *]" - Price higher than 500
  51 + def test_facet_search_with_query
  52 + records = Electronic.find_by_solr "memory", :facets => {:query => ["price:[* TO 200.00]",
  53 + "price:[200.00 TO 500.00]",
  54 + "price:[500.00 TO *]"]}
  55 + assert_equal 4, records.docs.size
  56 + assert_equal({"facet_queries" => {"price_rf:[* TO 200.00]"=>2,
  57 + "price_rf:[200.00 TO 500.00]"=>1,
  58 + "price_rf:[500.00 TO *]"=>1},
  59 + "facet_fields" => {}, "facet_dates" => {}}, records.facets)
  60 + end
  61 +
  62 + # Faceted search specifying the query and fields
  63 + def test_facet_search_with_query_and_field
  64 + records = Electronic.find_by_solr "memory", :facets => {:query => ["price:[* TO 200.00]",
  65 + "price:[200.00 TO 500.00]",
  66 + "price:[500.00 TO *]"],
  67 + :fields => [:category, :manufacturer]}
  68 +
  69 + q = records.facets["facet_queries"]
  70 + assert_equal 2, q["price_rf:[* TO 200.00]"]
  71 + assert_equal 1, q["price_rf:[500.00 TO *]"]
  72 + assert_equal 1, q["price_rf:[200.00 TO 500.00]"]
  73 +
  74 + f = records.facets["facet_fields"]
  75 + assert_equal 1, f["category_facet"]["Electronics"]
  76 + assert_equal 2, f["category_facet"]["Memory"]
  77 + assert_equal 1, f["category_facet"]["Hard Drive"]
  78 + assert_equal 1, f["manufacturer_facet"]["Samsung Electronics Co. Ltd."]
  79 + assert_equal 1, f["manufacturer_facet"]["Corsair Microsystems Inc."]
  80 + assert_equal 1, f["manufacturer_facet"]["A-DATA Technology Inc."]
  81 + assert_equal 1, f["manufacturer_facet"]["Apple Computer Inc."]
  82 + end
  83 +
  84 + # Faceted searches with :sort and :zeros options turned on/off
  85 + def test_faceted_search_using_zero_and_sort
  86 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:category]}
  87 + assert_equal({"category_facet"=>{"Electronics"=>1, "Memory"=>2, "Hard Drive"=>1}}, records.facets['facet_fields'])
  88 +
  89 + records = Electronic.find_by_solr "memory", :facets => {:sort => true, :fields =>[:category]}
  90 + assert_equal({"category_facet"=>{"Memory"=>2, "Electronics"=>1, "Hard Drive"=>1}}, records.facets['facet_fields'])
  91 +
  92 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:manufacturer]}
  93 + assert_equal({"manufacturer_facet" => {"Dell, Inc" => 0,
  94 + "Samsung Electronics Co. Ltd." => 1,
  95 + "Corsair Microsystems Inc." => 1,
  96 + "A-DATA Technology Inc." => 1,
  97 + "Apple Computer Inc." => 1}}, records.facets['facet_fields'])
  98 +
  99 + records = Electronic.find_by_solr "memory", :facets => {:zeros => false, :fields =>[:manufacturer]}
  100 + assert_equal({"manufacturer_facet" => {"Samsung Electronics Co. Ltd." => 1,
  101 + "Corsair Microsystems Inc." => 1,
  102 + "A-DATA Technology Inc." => 1,
  103 + "Apple Computer Inc." => 1}}, records.facets['facet_fields'])
  104 + end
  105 +
  106 + # Faceted search with 'drill-down' option being passed.
  107 + # The :browse option receives the argument in the format:
  108 + # "facet_field:term". You can drill-down to as many
  109 + # facet fields as you like
  110 + def test_faceted_search_with_drill_down
  111 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:category]}
  112 + assert_equal 4, records.docs.size
  113 + assert_equal({"category_facet"=>{"Electronics"=>1, "Memory"=>2, "Hard Drive"=>1}}, records.facets['facet_fields'])
  114 +
  115 + records = Electronic.find_by_solr "memory", :facets => {:fields =>[:category],
  116 + :browse => "category:Memory",
  117 + :zeros => false}
  118 + assert_equal 2, records.docs.size
  119 + assert_equal({"category_facet"=>{"Memory"=>2}}, records.facets['facet_fields'])
  120 + end
  121 +
  122 + def test_faceted_search_with_dates
  123 + records = Electronic.find_by_solr "memory", :facets => {:dates => {:fields => [:created_at, :updated_at],
  124 + :start => (Date.today - 7.years).strftime("%Y-%m-%dT%H:%M:%SZ"), :end => Date.today.strftime("%Y-%m-%dT%H:%M:%SZ"), :gap => '+1YEAR', :other => :all}}
  125 +
  126 + assert_equal 4, records.docs.size
  127 +
  128 + assert_equal 0, records.facets["facet_dates"]["created_at_d"]["after"]
  129 + assert_equal 1, records.facets["facet_dates"]["created_at_d"]["before"]
  130 + assert_equal 3, records.facets["facet_dates"]["created_at_d"]["between"]
  131 +
  132 + assert_equal 0, records.facets["facet_dates"]["updated_at_d"]["after"]
  133 + assert_equal 0, records.facets["facet_dates"]["updated_at_d"]["before"]
  134 + assert_equal 4, records.facets["facet_dates"]["updated_at_d"]["between"]
  135 + end
  136 +
  137 + def test_faceted_search_with_dates_filter
  138 + records = Electronic.find_by_solr "memory", :facets => {:dates => {:filter => ["updated_at:[#{(Date.today - 3.months).strftime("%Y-%m-%dT%H:%M:%SZ")} TO NOW-1MONTH/DAY]"]}}
  139 +
  140 + assert_equal 2, records.docs.size
  141 +
  142 + records.docs.each { |r|
  143 + assert r.updated_at >= (Date.today - 3.month)
  144 + assert r.updated_at <= (Date.today - 1.month)
  145 + }
  146 + end
  147 +
  148 + def test_faceted_search_with_dates_filter_and_facets
  149 + # this is a very contrived example but gives us data to validate
  150 + records = Electronic.find_by_solr "memory", :facets => {:dates => {:filter => ["updated_at:[#{(Date.today - 3.months).strftime("%Y-%m-%dT%H:%M:%SZ")} TO NOW-1MONTH/DAY]"],
  151 + :fields => [:created_at, :updated_at], :start => 'NOW-2MONTHS/DAY', :end => 'NOW-1MONTH/DAY', :gap => '+1MONTH', :other => :all}}
  152 +
  153 + assert_equal 2, records.docs.size
  154 +
  155 + assert_equal 0, records.facets["facet_dates"]["created_at_d"]["after"]
  156 + assert_equal 2, records.facets["facet_dates"]["created_at_d"]["before"]
  157 + assert_equal 0, records.facets["facet_dates"]["created_at_d"]["between"]
  158 +
  159 + assert_equal 0, records.facets["facet_dates"]["updated_at_d"]["after"]
  160 + assert_equal 1, records.facets["facet_dates"]["updated_at_d"]["before"]
  161 + assert_equal 1, records.facets["facet_dates"]["updated_at_d"]["between"]
  162 + end
  163 +end
... ...