blacklight_cql 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +142 -0
  3. data/Rakefile +43 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/blacklight_cql/explain_controller.rb +17 -0
  6. data/app/helpers/blacklight_cql/explain_helper.rb +44 -0
  7. data/app/views/blacklight_cql/explain/explain.xml.builder +30 -0
  8. data/config/routes.rb +10 -0
  9. data/init.rb +1 -0
  10. data/install.rb +1 -0
  11. data/lib/blacklight_cql/blacklight_to_solr.rb +115 -0
  12. data/lib/blacklight_cql/solr_helper_extension.rb +24 -0
  13. data/lib/blacklight_cql/template_helper_extension.rb +19 -0
  14. data/lib/blacklight_cql.rb +25 -0
  15. data/rails/init.rb +32 -0
  16. data/spec/app_root/app/controllers/application_controller.rb +2 -0
  17. data/spec/app_root/config/boot.rb +115 -0
  18. data/spec/app_root/config/database.yml +31 -0
  19. data/spec/app_root/config/environment.rb +30 -0
  20. data/spec/app_root/config/environments/in_memory.rb +0 -0
  21. data/spec/app_root/config/environments/mysql.rb +0 -0
  22. data/spec/app_root/config/environments/postgresql.rb +0 -0
  23. data/spec/app_root/config/environments/sqlite.rb +0 -0
  24. data/spec/app_root/config/environments/sqlite3.rb +0 -0
  25. data/spec/app_root/config/routes.rb +4 -0
  26. data/spec/app_root/lib/blacklight/search_fields.rb +97 -0
  27. data/spec/app_root/lib/console_with_fixtures.rb +4 -0
  28. data/spec/blacklight_to_solr_spec.rb +50 -0
  29. data/spec/data/luke.yaml +75 -0
  30. data/spec/data/zeerex-2.0.xsd +482 -0
  31. data/spec/marc_data/chomsky.mrc +1 -0
  32. data/spec/marc_data/social_journal.mrc +1 -0
  33. data/spec/rcov.opts +2 -0
  34. data/spec/spec.opts +4 -0
  35. data/spec/spec_helper.rb +73 -0
  36. data/spec/views/explain.xml.builder_spec.rb +125 -0
  37. data/tasks/blacklight_cql_tasks.rake +4 -0
  38. data/uninstall.rb +1 -0
  39. metadata +146 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,142 @@
1
+ = Blacklight-cql
2
+
3
+ An extension for the Blacklight solr search front-end.
4
+
5
+ http://projectblacklight.org
6
+
7
+ http://github.com/projectblacklight/blacklight
8
+
9
+ Provides for CQL search queries that map to Solr and Blacklight fields.
10
+
11
+
12
+ = Installation
13
+
14
+ Add to your local app's environment.rb:
15
+ config.gem "blacklight_cql"
16
+
17
+ Run "rake gems:install". That's it, you now have CQL support in your Blacklight.
18
+
19
+ See below for optional configuration to change default behavior.
20
+
21
+ = Use
22
+
23
+ http://your.blacklight.com/catalog?search_field=cql&q=[uri-encoded cql query]
24
+
25
+ Or for an atom response for instance,
26
+
27
+ http://your.blacklight.com/catalog.atom?search_field=cql&q=[uri-encoded cql query]
28
+
29
+ See http://www.loc.gov/standards/sru/specs/cql.html for more info on CQL syntax and semantics.
30
+
31
+ Any search_field you have configured in Blacklight.config[:search_fields] (probably in your config/blacklight_config.rb) is available as a CQL index. These search fields are only available with the custom "solr.dismax" CQL relation, taking a CQL expression as a value.
32
+
33
+ some_field solr.dismax "term +required -negate \"a phrase\" "
34
+
35
+ For dismax fields, the "=" server-choice relation means the same thing:
36
+
37
+ some_field = "term +required -negate \"a phrase\" "
38
+
39
+ Any Solr indexed field is also available as a CQL index. A much greater range of CQL relations are supported when you specify a Solr indexed field directly.
40
+
41
+ solr_field cql.adj "some phrase" AND solr_year within "1990 2000"
42
+
43
+ Solr indexed field CQL support is provided by the cql_ruby gem, for details on relations supported see: http://cql-ruby.rubyforge.org/svn/trunk/lib/cql_ruby/cql_to_solr.rb
44
+
45
+ If there is a direct solr indexed field with the same name as a Blacklight-configured dismax field, the BL field will take precedence. You can use explicit CQL "context set" prefixes to disambiguate.
46
+
47
+ [lsolr.field]
48
+ "lsolr" prefix means a direct solr indexed field
49
+ [local.some_field]
50
+ "local" prefix means a dismax field configured in Blacklight.config[:search_fields]
51
+
52
+ These prefixes can be changed, see configuration below.
53
+
54
+ Raw solr fields and Blacklight config'ed fields CAN be mixed together in a single CQL query.
55
+
56
+ (lsolr.title_t any "one two three" AND lsolr.author_t all "smith john") OR local.title = "my dismax title query"
57
+
58
+ CQL *does* need to be URL escaped in a URL, of course:
59
+
60
+ http://some.blacklight.com/catalog.atom?search_field=cql&q=%28lsolr.title_t+any+%22one+two+three%22+AND+lsolr.author_t+all+%22smith+john%22%29+OR+local.title+%3D+%22my+dismax+title+query%22
61
+
62
+ For "solr.dismax" or "=" relations, the the cql.serverChoice index maps to your default blacklight-configed field and solr.dismax relation. Otherwise, it maps to a direct solr field query with "adj", and the solr indexed field generally ends up being "text", although this depends on your configuration.
63
+
64
+ = SRU/ZeeRex Explain
65
+
66
+ This plugin does *not* provide a full SRU server. However, a ZeeRex/SRU explain document is provided by the plugin to advertise, in machine-readable format, what CQL indexes (ie, search fields) are provided by the server, and what relations are supported on each search field.
67
+
68
+ The explain document can be found at /catalog/explain on your server. (This URL end-point is not presently configurable).
69
+
70
+ For solr fields themselves, the plugin finds them via a Solr luke request, looking for any field that is Indexed in solr, and advertising it. (If you have configured lucene indexes directly not through solr, they will likely be erroneously included in the explain as well).
71
+
72
+ For Blacklight fields, Blacklight.config[:search_fields] is used to discover fields to put in the Explain.
73
+
74
+ Note that at present only the custom solr.dismax relation is supported on Blacklight fields. Most of the standard CQL relations are supported on raw solr fields.
75
+
76
+
77
+ = Configuration
78
+
79
+ == URL cql key, and cql label
80
+
81
+ A psuedo-blacklight-search-field is added by the Cql plugin to indicate a CQL search in the URL and BL processing. You can change the definition of this psuedo-field however you want to change the URL search_field key, the label for a CQL search echoed back to the user in HTML, or even to add some additional Solr parameters for the top-level Solr query for CQL searches. The value is a hash with the same semantics as other Blacklight.config[:search_fields] elements.
82
+
83
+ In an initializer:
84
+ BlacklightCql::SolrHelperExtension.psuedo_search_field = {
85
+ :key => "super_search",
86
+ :display_label => "The Super Search",
87
+ :solr_parameters => { "mm" => "100%" }
88
+ }
89
+
90
+ == Dismax search field configuration
91
+
92
+ All fields configured in Blacklight.config[:search_fields] are available as CQL indexes. If you'd like to make more dismax-configured search fields available in CQL but not the standard HTML search select menu, simply add them with :show_in_simple_select = false, eg:
93
+
94
+ Blacklight.config[:search_fields] << {:key => "only_in_cql", :show_in_simple_select => false, :local_solr_parameters => { :qf => "$my_special_qf"}}
95
+
96
+ As in the example above, you may want to use :local_solr_parameters referencing dollar-sign parameters that will be defined in your solrconfig.xml and de-referenced by Solr. This will keep your CQL-generated Solr querries a lot more readable in your logs and debugging. However, this feature is not available in Blacklight 2.5 (edge, or future 2.6).
97
+
98
+ Simply supplying literal values in :solr_paramaters is also supported and will work fine, it will just result in very long search querries in your solr query log.
99
+
100
+ == CQL context set prefixes
101
+
102
+ You can change the CQL "context set" prefix used for specifying a CQL index that is a direct solr field, or a Blacklight dismax configured field. In a Rails initializer:
103
+
104
+ CqlRuby.to_solr_defaults[:solr_field_prefix] = "my_solr"
105
+ CqlRuby.to_solr_defaults[:blacklight_field_prefix] = "my_blacklight_fields"
106
+
107
+ == Defaults from CqlRuby for direct solr indexed field querries.
108
+
109
+ For direct-solr-field operations, there are additional defaults that can be set, supported by CqlRuby. See: http://cql-ruby.rubyforge.org/svn/trunk/lib/cql_ruby/cql_to_solr.rb
110
+
111
+ Eg:
112
+
113
+ CqlRuby.to_solr_defaults[:default_index] = "solr_index"
114
+ CqlRuby.to_solr_defaults[:all_index] = "solr_mega_index"
115
+ CqlRuby.to_solr_defaults[:default_relation] = "cql.any"
116
+
117
+ = TO DO
118
+
119
+ * Support more CQL relations on blacklight dismax fields. Right now only dismax queries are supported. We could also support:
120
+ * cql.all (set mm to 100%)
121
+ * cql.adj (phrase search with qs set to 0)
122
+ * cql.any
123
+ * range querries on dismax fields? Maybe. <, <=, >, >=, within
124
+ * <> on dismax fields, similar to how it works on raw solr.
125
+ * Is there a simple way to support CQL PROX boolean operator? Not sure. That's a weird operator in CQL, it makes it possible to specify things which make no sense, like onefield = val PROX anotherfield=val2
126
+ * Support CQL sortBy clauses mapped to Blacklight sort param. We can't tell which solr fields are available for sorting solely from luke, will need additional config to advertise in Explain.
127
+ * Add CQL relation modifiers on solr.dismax that let you specify arbitrary solr/dismax query parameters. (Add to solr context set too).
128
+ * Support relation modifier on cql.adj that maps to qs
129
+ * support CQL context set 'fuzzy' modifier, to have some effect on mm, ps, qs, etc.
130
+ * *Big one*: Figure out how to embed the explain and advertise the CQL in the BL OpenSearch description (including OpenSearch response in Atom). Tricky from BL architecture to inject this into BL, also some dispute about the best way for the actual XML to look to support this.
131
+
132
+ = Use without Blacklight?
133
+
134
+ Most of the code in this plugin was written to potentially be useful in other projects, not Blacklight, not neccesarily even Rails. However, the gem initialization code assumes Blacklight in order to insert it's hooks into Blacklight properly. This can probably be refactored to make it easier to use this gem in a non-BL or even non-Rails app, let me know if you are someone who has an actual need/plan for this, and I can possibly help.
135
+
136
+ = Acknolwedgements
137
+
138
+ Thanks to Chick Markley for writing the CqlRuby gem that provides the fundamental functionality here, and for making me a committer on the project. Thanks to Mike Taylor for writing the original Java CQL parser that Chick's work was based on.
139
+
140
+
141
+
142
+ Copyright 2010 Jonathan Rochkind/Johns Hopkins University, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the blacklight_cql plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the blacklight_cql plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'Blacklight-cql'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "blacklight_cql"
29
+ gemspec.summary = "Add CQL query support to a Blacklight app"
30
+ gemspec.description = "May have parts that can be used outside a Blacklight app too with a big of refactoring, let me know if interested."
31
+ gemspec.email = "rochkind@jhu.edu"
32
+ gemspec.homepage = "http://github.com/projectblacklight/blacklight_cql"
33
+ gemspec.authors = ["Jonathan Rochkind"]
34
+
35
+ gemspec.add_dependency("cql-ruby", ">=0.8.1")
36
+
37
+ gemspec.add_development_dependency("markup_validity")
38
+ end
39
+ Jeweler::GemcutterTasks.new
40
+ rescue LoadError
41
+ puts "Jeweler not available. Install it with: gem install jeweler"
42
+ end
43
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
@@ -0,0 +1,17 @@
1
+ require 'rsolr-ext'
2
+
3
+ module BlacklightCql
4
+ class ExplainController < ApplicationController
5
+ layout false
6
+
7
+ def index
8
+ @luke_response = Blacklight.solr.luke
9
+ @field_config = Blacklight
10
+
11
+ render "explain.xml.builder", :content_type => "application/xml"
12
+ end
13
+
14
+ end
15
+
16
+
17
+ end
@@ -0,0 +1,44 @@
1
+ module BlacklightCql::ExplainHelper
2
+
3
+ # Arg is a Builder instance.
4
+ def luke_to_explain_index(xml)
5
+ @luke_response[:fields].each_pair do |solr_field, defn|
6
+ # only if it's an indexed
7
+ if defn[:schema].include?("I")
8
+ xml.index("search" => "true", "scan"=>false, "sort" => false) do
9
+ xml.title solr_field.to_s
10
+ xml.map do
11
+ xml.name(solr_field.to_s, "set" => CqlRuby.to_solr_defaults[:solr_field_prefix])
12
+ end
13
+ # What relations do we support for this index?
14
+ xml.configInfo do
15
+ ["==", "=", ">=", ">", "<", "<=", "<>", "within", "adj", "all", "any"].each do |rel|
16
+ xml.supports(rel, "type"=>"relation")
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ # Expects an object in @field_config that implements
25
+ # Blacklight::SearchFields to provide search field config.
26
+ # argument is a Builder object we can write to.
27
+ def blacklight_config_to_explain_index(xml)
28
+ @field_config.search_field_list.each do |search_field|
29
+ xml.index("search" => "true", "scan" => "false", "sort" => "false") do
30
+ xml.title search_field[:display_label]
31
+ xml.map do
32
+ xml.name(search_field[:key], "set" => CqlRuby.to_solr_defaults[:blacklight_field_prefix])
33
+ end
34
+ # What relations do we support for this index? Right now,
35
+ # just the custom solr.dismax one
36
+ xml.configInfo do
37
+ xml.supports("=", "type"=>"relation")
38
+ xml.supports("solr.dismax", "type"=>"relation")
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,30 @@
1
+ xml.instruct!
2
+ xml.explain("xmlns" => "http://explain.z3950.org/dtd/2.0/") do
3
+
4
+ # Made up protocol "http-cql", oh well.
5
+ xml.serverInfo("protocol" => "http-cql") do
6
+ xml.host request.host
7
+ xml.port request.port
8
+ xml.database url_for(:controller => "/catalog", :action => "index", :search_field => BlacklightCql::SolrHelperExtension.pseudo_search_field[:key])
9
+ end
10
+
11
+ xml.indexInfo do
12
+ xml.set(:name => CqlRuby.to_solr_defaults[:solr_field_prefix],
13
+ :identifier =>
14
+ url_for(:controller => "/catalog",
15
+ :action => "index",
16
+ :anchor => "local_solr_field",
17
+ :only_path => false))
18
+ xml.set(:name => CqlRuby.to_solr_defaults[:blacklight_field_prefix],
19
+ :identifier =>
20
+ url_for(:controller => "/catalog",
21
+ :action => "index",
22
+ :anchor => "local_app_field",
23
+ :only_path => false))
24
+ #context set for our solr.dismax relation
25
+ xml.set(:name => "solr", :identifier => "http://purl.org/net/cql-context-set/solr")
26
+
27
+ blacklight_config_to_explain_index(xml)
28
+ luke_to_explain_index(xml)
29
+ end
30
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ # Fix this for Rails3, this is a weird way to do it, but oh well.
2
+ # We want to map catalog/explain but not touch the app's own mappings
3
+ # for catalog resource. So we do it with just old style routing.
4
+
5
+ ActionController::Routing::Routes.draw do |map|
6
+ map.connect 'catalog/explain', :controller => "blacklight_cql/explain", :action => "index"
7
+
8
+
9
+ end
10
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ File.dirname(__FILE__) + "/rails/init".
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,115 @@
1
+ require 'cql_ruby'
2
+
3
+ # Patch CqlRuby to provide a #to_bl_solr implementation, which is like
4
+ # to_solr, but aware of Blacklight search_field defintions for dismax relation
5
+ # queries.
6
+ #
7
+ # The relation "solr.dismax" is used to mean that the value string is a
8
+ # query in the solr dismax query parsing language.
9
+ #
10
+ # CQL indexes can refer to direct solr fields, or Blacklight-defined dismax
11
+ # search_fields, specified by CQL prefix. By default, "lsolr" means a local
12
+ # solr field, and "local" means a blacklight search_field. Indexes specified
13
+ # with no index will use a blacklight search_field if one exists, otherwise
14
+ # assumed to be solr field.
15
+ #
16
+ # "Context set" prefixed used to identify bl and solr fields
17
+ # can be changed in CqlRuby.to_solr_defaults, keys solr_field_prefix,
18
+ # blacklight_field_prefix.
19
+ #
20
+ # argument to #to_bl_solr is a 'config' object that duck-types to
21
+ # Blacklight::SearchFields , for accessing search fields config.
22
+ module CqlRuby
23
+ to_solr_defaults[:solr_field_prefix] = "lsolr"
24
+ to_solr_defaults[:blacklight_field_prefix] = "local"
25
+ to_solr_defaults[:dismax_relation] = "solr.dismax"
26
+
27
+
28
+ CqlNode.send(:include,
29
+ Module.new do
30
+ def to_bl_solr(config)
31
+ raise CqlException.new("#to_bl_solr not supported for #{self.class}: #{self.to_cql}")
32
+ end
33
+ end
34
+ )
35
+
36
+
37
+ CqlBooleanNode.send(:include,
38
+ Module.new do
39
+ def to_bl_solr(config)
40
+ "( #{@left_node.to_bl_solr(config) } #{@modifier_set.to_solr} #{@right_node.to_bl_solr(config) } )"
41
+ end
42
+ end
43
+ )
44
+
45
+
46
+ CqlTermNode.send(:include,
47
+ Module.new do
48
+ def to_bl_solr(config)
49
+ (index_prefix, index) = parse_index(@index, config)
50
+
51
+ if (index_prefix == CqlRuby.to_solr_defaults[:blacklight_field_prefix])
52
+ field_def = config.search_field_def_for_key(index)
53
+
54
+ # Merge together solr params and local ones; they're ALL
55
+ # going to be provided as LocalParams in our nested query.
56
+ # Merge so :solr_local_parameters take precedence.
57
+ per_field_params = (field_def[:solr_parameters] || {}).merge( field_def[:solr_local_parameters] || {} )
58
+
59
+ local_params =
60
+ per_field_params.collect do |key, val|
61
+ key.to_s + "=" + BlacklightCql.solr_param_quote(val)
62
+ end.join(" ")
63
+
64
+ relation = @relation.modifier_set.base
65
+ relation = "solr.dismax" if relation == "=" # default server choice
66
+ relation = "cql.#{relation}" unless relation.index(".") || ["<>", "<=", ">=", "<", ">", "=", "=="].include?(relation)
67
+
68
+
69
+ case relation
70
+ when "solr.dismax"
71
+ return " _query_:\"{!dismax #{local_params}} #{BlacklightCql.escape_quotes(@term)} \" "
72
+ else
73
+ raise CqlException.new("relation #{relation} not supported for #{CqlRuby.to_solr_defaults[:blacklight_field_prefix]} indexes: #{self.to_cql}")
74
+ end
75
+
76
+ elsif (index_prefix == CqlRuby.to_solr_defaults[:solr_field_prefix])
77
+ return to_solr
78
+ else
79
+ raise CqlException.new("Index prefix not recognized: #{index_prefix}")
80
+ end
81
+ end
82
+
83
+
84
+ protected
85
+
86
+ # Parse CQL index string into namespace and base name, filling in default
87
+ # namespace. Returns array of [namespace, basename]
88
+ def parse_index(index, config)
89
+ index_prefix, index = @index.index(".") ?
90
+ @index.split(".", 2) :
91
+ [nil, @index]
92
+
93
+ #for cql.serverChoice and dismax relation, we do dismax with
94
+ #default field, otherwise we'll let it fall through to #to_solr
95
+ if( @index.downcase == "cql.serverchoice" &&
96
+ [CqlRuby.to_solr_defaults[:dismax_relation].downcase,
97
+ "="].include?(@relation.modifier_set.base.downcase)
98
+ )
99
+ index_prefix = CqlRuby.to_solr_defaults[:blacklight_index_prefix]
100
+ index = config.default_search_field[:key]
101
+ end
102
+ # If no prefix, we use local one if we can
103
+ if ( index_prefix == nil)
104
+ if ( config.search_field_def_for_key(index) )
105
+ index_prefix = CqlRuby.to_solr_defaults[:blacklight_field_prefix]
106
+ else
107
+ index_prefix = CqlRuby.to_solr_defaults[:solr_field_prefix]
108
+ end
109
+ end
110
+ return [index_prefix, index]
111
+ end
112
+ end
113
+ )
114
+
115
+ end
@@ -0,0 +1,24 @@
1
+ # We over-ride methods on CatalogController simply by include'ing this
2
+ # module into CatalogController, which this plugins setup will do.
3
+ #
4
+ # This works ONLY becuase the methods we're over-riding come from
5
+ # a module themsleves (SolrHelper) -- if they were defined on CatalogController
6
+ # itself, it would not, and we'd have to use some ugly monkey patching
7
+ # alias_method_chain instead, thankfully we do not.
8
+ module BlacklightCql::SolrHelperExtension
9
+ mattr_accessor :pseudo_search_field
10
+ self.pseudo_search_field = {:key => "cql", :display_label => "External Search (CQL)", :include_in_simple_select => false}
11
+
12
+ # Over-ride solr_search_params to do special CQL-to-complex-solr-query
13
+ # processing iff the "search_field" is our pseudo-search-field indicating
14
+ # a CQL query.
15
+ def solr_search_params(extra_controller_params = {})
16
+ solr_params = super(extra_controller_params)
17
+
18
+ if params["search_field"] == self.pseudo_search_field[:key] && ! params["q"].blank?
19
+ parser = CqlRuby::CqlParser.new
20
+ solr_params[:q] = "{!lucene} " + parser.parse( params["q"] ).to_bl_solr(Blacklight)
21
+ end
22
+ return solr_params
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ # We can over-ride a default Blacklight template helper and still call
2
+ # super on it, by inserting this module as a helper into CatalogController.
3
+ # This plugins setup will do that.
4
+ module BlacklightCql::TemplateHelperExtension
5
+
6
+ # Make sure the CQL pseudo-search_field is included in the 'select'
7
+ # when we're displaying a CQL search, so the select makes sense.
8
+ def search_fields
9
+ field = BlacklightCql::SolrHelperExtension.pseudo_search_field
10
+
11
+ if params[:q].blank? || params[:search_field] != field[:key]
12
+ super
13
+ else
14
+ super.clone.push([field[:display_label], field[:key]]).uniq
15
+ end
16
+ end
17
+
18
+
19
+ end
@@ -0,0 +1,25 @@
1
+ # Blacklight-cql
2
+
3
+ require 'blacklight_cql/blacklight_to_solr'
4
+
5
+ module BlacklightCql
6
+ mattr_accessor :cql_search_field_key
7
+ self.cql_search_field_key = "cql"
8
+
9
+ # Escape single or double quote marks with backslash
10
+ def self.escape_quotes(input)
11
+ input.gsub("'", "\\\'").gsub('"', "\\\"")
12
+ end
13
+
14
+ # Escapes value for Solr LocalParam. Will wrap in
15
+ # quotes only if needed (if not needed, and the value
16
+ # turns out to have been a $param, then quotes will mess
17
+ # things up!), and escapes value inside quotes.
18
+ def self.solr_param_quote(val)
19
+ unless val =~ /^[a-zA-Z$_\-\^]+$/
20
+ val = "'" + escape_quotes(val) + "'"
21
+ end
22
+ return val
23
+ end
24
+
25
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,32 @@
1
+ # Include hook code here
2
+ require 'cql_ruby'
3
+ require 'blacklight_cql'
4
+ require 'dispatcher'
5
+
6
+
7
+ # Call in after_initialze to make sure the default search_fields are
8
+ # already created, AND the local app has had the opportunity to customize
9
+ # our placeholder search_field.
10
+ config.after_initialize do
11
+ Blacklight.config[:search_fields] << BlacklightCql::SolrHelperExtension.pseudo_search_field
12
+ end
13
+
14
+ # Wrapping in Dispatcher.to_prepare will, theoretically, take care of things
15
+ # working properly even in development mode with cache_classes=false (per-request
16
+ # class reloading).
17
+ Dispatcher.to_prepare("blacklight_cql.setup") do
18
+
19
+
20
+ #Check in case CatalogController _hasn't_ really been re-loaded
21
+ unless (CatalogController.kind_of?( BlacklightCql::SolrHelperExtension ))
22
+ # Will over-ride #solr_params to deal with CQL
23
+ CatalogController.send(:include, BlacklightCql::SolrHelperExtension)
24
+
25
+ # Will over-ride helper methods for search form select, to ensure
26
+ # query is echo'd properly.
27
+ CatalogController.send(:helper, BlacklightCql::TemplateHelperExtension)
28
+ end
29
+
30
+
31
+ end
32
+
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,115 @@
1
+ # Allow customization of the rails framework path
2
+ RAILS_FRAMEWORK_ROOT = (ENV['RAILS_FRAMEWORK_ROOT'] || "#{File.dirname(__FILE__)}/../../../../../../vendor/rails") unless defined?(RAILS_FRAMEWORK_ROOT)
3
+
4
+ # Don't change this file!
5
+ # Configure your app in config/environment.rb and config/environments/*.rb
6
+
7
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
8
+
9
+ module Rails
10
+ class << self
11
+ def boot!
12
+ unless booted?
13
+ preinitialize
14
+ pick_boot.run
15
+ end
16
+ end
17
+
18
+ def booted?
19
+ defined? Rails::Initializer
20
+ end
21
+
22
+ def pick_boot
23
+ (vendor_rails? ? VendorBoot : GemBoot).new
24
+ end
25
+
26
+ def vendor_rails?
27
+ File.exist?(RAILS_FRAMEWORK_ROOT)
28
+ end
29
+
30
+ def preinitialize
31
+ load(preinitializer_path) if File.exist?(preinitializer_path)
32
+ end
33
+
34
+ def preinitializer_path
35
+ "#{RAILS_ROOT}/config/preinitializer.rb"
36
+ end
37
+ end
38
+
39
+ class Boot
40
+ def run
41
+ load_initializer
42
+ Rails::Initializer.run(:set_load_path)
43
+ end
44
+ end
45
+
46
+ class VendorBoot < Boot
47
+ def load_initializer
48
+ require "#{RAILS_FRAMEWORK_ROOT}/railties/lib/initializer"
49
+ Rails::Initializer.run(:install_gem_spec_stubs)
50
+ Rails::GemDependency.add_frozen_gem_path
51
+ end
52
+ end
53
+
54
+ class GemBoot < Boot
55
+ def load_initializer
56
+ self.class.load_rubygems
57
+ load_rails_gem
58
+ require 'initializer'
59
+ end
60
+
61
+ def load_rails_gem
62
+ if version = self.class.gem_version
63
+ gem 'rails', version
64
+ else
65
+ gem 'rails'
66
+ end
67
+ rescue Gem::LoadError => load_error
68
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
69
+ exit 1
70
+ end
71
+
72
+ class << self
73
+ def rubygems_version
74
+ Gem::RubyGemsVersion rescue nil
75
+ end
76
+
77
+ def gem_version
78
+ if defined? RAILS_GEM_VERSION
79
+ RAILS_GEM_VERSION
80
+ elsif ENV.include?('RAILS_GEM_VERSION')
81
+ ENV['RAILS_GEM_VERSION']
82
+ else
83
+ parse_gem_version(read_environment_rb)
84
+ end
85
+ end
86
+
87
+ def load_rubygems
88
+ require 'rubygems'
89
+ min_version = '1.3.1'
90
+ unless rubygems_version >= min_version
91
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
92
+ exit 1
93
+ end
94
+
95
+ rescue LoadError
96
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
97
+ exit 1
98
+ end
99
+
100
+ def parse_gem_version(text)
101
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
102
+ end
103
+
104
+ private
105
+ def read_environment_rb
106
+ environment_rb = "#{RAILS_ROOT}/config/environment.rb"
107
+ environment_rb = "#{HELPER_RAILS_ROOT}/config/environment.rb" unless File.exists?(environment_rb)
108
+ File.read(environment_rb)
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ # All that for this:
115
+ Rails.boot!