jakewendt-active_record_sunspotter 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.rdoc +26 -0
- data/lib/active_record_sunspotter.rb +18 -0
- data/lib/active_record_sunspotter/rails/engine.rb +9 -0
- data/lib/active_record_sunspotter/rails/railtie.rb +12 -0
- data/lib/active_record_sunspotter/search_sunspot_for.rb +247 -0
- data/lib/active_record_sunspotter/sunspot_column.rb +51 -0
- data/lib/active_record_sunspotter/sunspot_helper.rb +187 -0
- data/lib/active_record_sunspotter/sunspot_rails_server.rb +15 -0
- data/lib/active_record_sunspotter/sunspotability.rb +127 -0
- data/lib/jakewendt-active_record_sunspotter.rb +1 -0
- data/vendor/assets/javascripts/sunspot.js +27 -0
- data/vendor/assets/stylesheets/sunspot.css.scss +207 -0
- data/vendor/views/layouts/_footer.html.erb +5 -0
- data/vendor/views/layouts/_sunspot_head.html.erb +14 -0
- data/vendor/views/layouts/_sunspot_header.html.erb +5 -0
- data/vendor/views/layouts/sunspot.html.erb +22 -0
- data/vendor/views/sunspot/_download_csv.html.erb +5 -0
- data/vendor/views/sunspot/_facets.html.erb +15 -0
- data/vendor/views/sunspot/_filters.html.erb +16 -0
- data/vendor/views/sunspot/_link.html.erb +1 -0
- data/vendor/views/sunspot/_per_page.html.erb +4 -0
- data/vendor/views/sunspot/_results.html.erb +24 -0
- data/vendor/views/sunspot/_select_columns.html.erb +21 -0
- data/vendor/views/sunspot/index.csv.erb +25 -0
- data/vendor/views/sunspot/index.json.erb +25 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: efc5d2892a72ebadab5312d46eb1bab1d30821be
|
4
|
+
data.tar.gz: f58e5becead798a846087b9ce8641ac8a705b90e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15fd738ed93098d20eb9e563c73f90f7d0818e1e8956fefd2ad5bf4b54de3f4f4752597a66a7053dee7e7ec0bf672bb1112337b4e16ea086c02f725c931c50a8
|
7
|
+
data.tar.gz: 3f6705aa87f2e0a77ea34f37b9f14bf43f90f179d41b019da958f61b2613eb8797066536e484cc5e2ea51ff65d456abd389275a55358e636362781c54a7d92e9
|
data/README.rdoc
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
= active_record_sunspotter
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
== Gemified with Jeweler
|
13
|
+
|
14
|
+
vi Rakefile
|
15
|
+
rake version:write
|
16
|
+
|
17
|
+
rake version:bump:patch
|
18
|
+
rake version:bump:minor
|
19
|
+
rake version:bump:major
|
20
|
+
|
21
|
+
rake gemspec
|
22
|
+
|
23
|
+
rake install
|
24
|
+
rake release
|
25
|
+
|
26
|
+
Copyright (c) 2010 [Jake Wendt], released under the MIT license
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
# why do I have to explicitly require it here, but not when gem included in app's Gemfile?
|
3
|
+
require 'sunspot_rails'
|
4
|
+
|
5
|
+
module ActiveRecordSunspotter; end
|
6
|
+
require 'active_record_sunspotter/sunspot_rails_server'
|
7
|
+
require 'active_record_sunspotter/sunspot_column'
|
8
|
+
require 'active_record_sunspotter/sunspotability'
|
9
|
+
require 'active_record_sunspotter/search_sunspot_for'
|
10
|
+
require 'active_record_sunspotter/sunspot_helper'
|
11
|
+
|
12
|
+
|
13
|
+
if defined?(Rails)
|
14
|
+
require 'active_record_sunspotter/rails/engine'
|
15
|
+
require 'active_record_sunspotter/rails/railtie'
|
16
|
+
|
17
|
+
ActionController::Base.append_view_path( File.join(File.dirname(__FILE__), '../vendor/views'))
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ActiveRecordSunspotter
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer 'add_helpers_to_actionview' do |app|
|
4
|
+
ActiveSupport.on_load :action_view do
|
5
|
+
ActionView::Base.send(:include, ActiveRecordSunspotter::SunspotHelper)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
# rake_tasks do
|
9
|
+
# Dir["#{File.dirname(__FILE__)}/../tasks/**/*.rake"].sort.each { |ext| load ext }
|
10
|
+
# end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
module ActiveRecordSunspotter::SearchSunspotFor
|
2
|
+
|
3
|
+
def search_sunspot_for( search_class )
|
4
|
+
@sunspot_search_class = search_class
|
5
|
+
|
6
|
+
# Formerly a before_filter, but after being genericized,
|
7
|
+
# we don't know the search class until the search begins.
|
8
|
+
@sunspot_search_class.methods.include?(:solr_search) ||
|
9
|
+
access_denied("Sunspot server probably wasn't started first!", root_path)
|
10
|
+
|
11
|
+
#
|
12
|
+
# Something has changed that causes this now when :search is stubbed.
|
13
|
+
# Unstubbing makes testing raise and error and basically stop.
|
14
|
+
#
|
15
|
+
#/opt/local/lib/ruby2.0/gems/2.0.0/gems/mocha-0.13.3/lib/mocha/class_method.rb:80:in `public': undefined method `search' for class `Class' (NameError)
|
16
|
+
#
|
17
|
+
# changing this to use solr_search rather than search seems to make it ok.
|
18
|
+
# search is actually an alias to solr_search which may be the cause.
|
19
|
+
# new ruby, new rules? Perhaps a newer mocha would work, but I've had
|
20
|
+
# a number of problems with newer versions.
|
21
|
+
#
|
22
|
+
|
23
|
+
begin
|
24
|
+
@search = @sunspot_search_class.solr_search do
|
25
|
+
|
26
|
+
if params[:q].present?
|
27
|
+
fulltext params[:q]
|
28
|
+
end
|
29
|
+
|
30
|
+
self.instance_variable_get('@setup').clazz.sunspot_all_filters.each do |f| # don't use |facet|
|
31
|
+
|
32
|
+
p=f.name
|
33
|
+
|
34
|
+
if f.range
|
35
|
+
|
36
|
+
range_facet_and_filter_for(p,params.dup,f.range)
|
37
|
+
|
38
|
+
elsif f.ranges # YES, PLURAL for an array of fixed ranges
|
39
|
+
|
40
|
+
fixed_range_facet_and_filter_for(p,params.dup,f.ranges)
|
41
|
+
|
42
|
+
else
|
43
|
+
|
44
|
+
if params[p]
|
45
|
+
#
|
46
|
+
# 20130423 - be advised that false.blank? is true so the boolean attributes
|
47
|
+
# will not work correctly here. Need to find another way.
|
48
|
+
# I don't use boolean columns anymore
|
49
|
+
#
|
50
|
+
params[p] = [params[p].dup].flatten.reject{|x|x.blank?}
|
51
|
+
|
52
|
+
if params[p+'_op'] && params[p+'_op'].match(/AND/i).present?
|
53
|
+
unless params[p].blank? # empty? # blank? works for arrays too
|
54
|
+
with(p).all_of params[p]
|
55
|
+
else
|
56
|
+
params.delete(p) # remove the key so doesn't show in view
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# NOTE This is an INTEGER SORT for the BETWEEN filter!
|
61
|
+
#
|
62
|
+
elsif params[p+'_op'] && params[p+'_op'].match(/BETWEEN/i).present?
|
63
|
+
unless params[p].blank? #empty? # blank? works for arrays too
|
64
|
+
# between is expecting an array with a first and last (can be array of 1 really)
|
65
|
+
with(p).between [params[p].sort_by(&:to_i)].flatten
|
66
|
+
else
|
67
|
+
params.delete(p) # remove the key so doesn't show in view
|
68
|
+
end
|
69
|
+
|
70
|
+
else # using 'OR'
|
71
|
+
unless params[p].blank? #empty? # blank? works for arrays too
|
72
|
+
with(p).any_of params[p]
|
73
|
+
else
|
74
|
+
params.delete(p) # remove the key so doesn't show in view
|
75
|
+
end
|
76
|
+
end # if params[p+'_op'] && params[p+'_op']=='AND'
|
77
|
+
|
78
|
+
end # if params[p]
|
79
|
+
|
80
|
+
# facet.sort
|
81
|
+
# This param determines the ordering of the facet field constraints.
|
82
|
+
# count - sort the constraints by count (highest count first)
|
83
|
+
# index - to return the constraints sorted in their index order
|
84
|
+
# (lexicographic by indexed term). For terms in the ascii range,
|
85
|
+
# this will be alphabetically sorted.
|
86
|
+
# The default is count if facet.limit is greater than 0, index otherwise.
|
87
|
+
# Prior to Solr1.4, one needed to use true instead of count and false instead of index.
|
88
|
+
# This parameter can be specified on a per field basis.
|
89
|
+
#
|
90
|
+
# put this inside the else condition as the if block is
|
91
|
+
# for ranges and it calls facet
|
92
|
+
facet p.to_sym, :sort => :index if f.facetable
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end # @sunspot_search_class.sunspot_all_filters.each do |p|
|
97
|
+
|
98
|
+
order_by *search_order
|
99
|
+
|
100
|
+
if request.format.to_s.match(/csv|json/)
|
101
|
+
# don't paginate csv file. Only way seems to be to make BIG query
|
102
|
+
# rather than the arbitrarily big number, I could possibly
|
103
|
+
# use the @search.total from the previous search sent as param?
|
104
|
+
paginate :page => 1, :per_page => 1000000
|
105
|
+
else
|
106
|
+
paginate :page => params[:page], :per_page => params[:per_page]||=50
|
107
|
+
end
|
108
|
+
end # @search = @sunspot_search_class.solr_search do
|
109
|
+
|
110
|
+
rescue Errno::ECONNREFUSED
|
111
|
+
flash[:error] = "Solr seems to be down for the moment."
|
112
|
+
redirect_to root_path
|
113
|
+
end # begin
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
def search_order
|
118
|
+
if params[:order] and @sunspot_search_class.sunspot_orderable_column_names.include?(
|
119
|
+
params[:order].downcase )
|
120
|
+
order_string = params[:order]
|
121
|
+
dir = case params[:dir].try(:downcase)
|
122
|
+
when 'desc' then 'desc'
|
123
|
+
else 'asc'
|
124
|
+
end
|
125
|
+
return order_string.to_sym, dir.to_sym
|
126
|
+
else
|
127
|
+
return :id, :asc
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
::Sunspot::DSL::Search.class_eval do
|
132
|
+
|
133
|
+
def range_filter_for(field,params={})
|
134
|
+
if params[field]
|
135
|
+
# "expect"=>["1e-5..1e0"]
|
136
|
+
any_of do
|
137
|
+
params[field].each do |pp|
|
138
|
+
# if pp =~ /^Under (\d+)$/
|
139
|
+
if pp =~ /^Under (.+)$/
|
140
|
+
with( field.to_sym ).less_than $1 # actually less than or equal to
|
141
|
+
# elsif pp =~ /^Over (\d+)$/
|
142
|
+
elsif pp =~ /^Over (.+)$/
|
143
|
+
with( field.to_sym ).greater_than $1 # actually greater than or equal to
|
144
|
+
# elsif pp =~ /^\d+\.\.\d+$/
|
145
|
+
elsif pp =~ /^.+\.\..+$/
|
146
|
+
with( field.to_sym, eval(pp) ) # NOTE could add parantheses then use Range.new( $1,$2 )???
|
147
|
+
elsif pp =~ /^\d+$/
|
148
|
+
with( field.to_sym, pp ) # primarily for testing? No range, just value
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def fixed_range_facet_and_filter_for(field,params={},options={})
|
156
|
+
range_filter_for(field,params)
|
157
|
+
facet field.to_sym do
|
158
|
+
options.each do |h|
|
159
|
+
# row "#{h[:name]}" do
|
160
|
+
# row "#{h[:between].sort.first}..#{h[:between].sort.last}" do
|
161
|
+
# with( field.to_sym, Range.new(h[:between].sort.first,h[:between].sort.last) )
|
162
|
+
row "#{h[:range]}" do
|
163
|
+
with( field.to_sym, h[:range] )
|
164
|
+
end
|
165
|
+
end # options.each do |h|
|
166
|
+
end # facet field.to_sym do
|
167
|
+
end
|
168
|
+
|
169
|
+
#
|
170
|
+
# what's the difference between
|
171
|
+
# with( field, Range.new(range,range+step) )
|
172
|
+
# and
|
173
|
+
# with( field ).between( [range,range+step] )
|
174
|
+
# I don't think that there is any
|
175
|
+
# Note that
|
176
|
+
# with( field, [range,range+step] )
|
177
|
+
# IS NOT THE SAME. That would be an ANY check.
|
178
|
+
# And using Range.new probably only works with integers.
|
179
|
+
# Not floats, doubles, text, dates, etc.
|
180
|
+
# Actually the example uses 3.0..5.0 but I'm not sure
|
181
|
+
# how ruby would interpret that. Base on the number of given decimal places?
|
182
|
+
# Exponential notation probably would not work.
|
183
|
+
#
|
184
|
+
|
185
|
+
def range_facet_and_filter_for(field,params={},options={})
|
186
|
+
start = (options[:start] || 20) #.to_i
|
187
|
+
stop = (options[:stop] || 50) #.to_i
|
188
|
+
step = (options[:step] || 10) #.to_i
|
189
|
+
log = (options[:log] || false) #.to_i
|
190
|
+
range_filter_for(field,params)
|
191
|
+
# if params[field]
|
192
|
+
## "expect"=>["1e-5..1e0"]
|
193
|
+
# any_of do
|
194
|
+
# params[field].each do |pp|
|
195
|
+
## if pp =~ /^Under (\d+)$/
|
196
|
+
# if pp =~ /^Under (.+)$/
|
197
|
+
# with( field.to_sym ).less_than $1 # actually less than or equal to
|
198
|
+
## elsif pp =~ /^Over (\d+)$/
|
199
|
+
# elsif pp =~ /^Over (.+)$/
|
200
|
+
# with( field.to_sym ).greater_than $1 # actually greater than or equal to
|
201
|
+
## elsif pp =~ /^\d+\.\.\d+$/
|
202
|
+
# elsif pp =~ /^.+\.\..+$/
|
203
|
+
# with( field.to_sym, eval(pp) ) # NOTE could add parantheses then use Range.new( $1,$2 )???
|
204
|
+
# elsif pp =~ /^\d+$/
|
205
|
+
# with( field.to_sym, pp ) # primarily for testing? No range, just value
|
206
|
+
# end
|
207
|
+
# end
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
facet field.to_sym do
|
211
|
+
if log
|
212
|
+
row "Under 1e#{start}" do
|
213
|
+
with( field.to_sym ).less_than "1e#{start}".to_f
|
214
|
+
end
|
215
|
+
(start..(stop-step)).step(step).each do |range|
|
216
|
+
row "1e#{range}..1e#{range+step}" do
|
217
|
+
with( field.to_sym, Range.new("1e#{range}".to_f,"1e#{range+step}".to_f) )
|
218
|
+
end
|
219
|
+
end
|
220
|
+
row "Over 1e#{stop}" do
|
221
|
+
with( field.to_sym ).greater_than "1e#{stop}".to_f
|
222
|
+
end
|
223
|
+
else
|
224
|
+
# row "text label for facet in view", block for facet.query
|
225
|
+
row "Under #{start}" do
|
226
|
+
# Is less_than just less_than or does it also include equal_to?
|
227
|
+
# Results appear to include equal_to which makes it actually incorrect and misleading.
|
228
|
+
with( field.to_sym ).less_than start # facet query to pre-show count if selected (NOT A FILTER)
|
229
|
+
end
|
230
|
+
# this works when like 1-100 step 10
|
231
|
+
(start..(stop-step)).step(step).each do |range|
|
232
|
+
row "#{range}..#{range+step}" do
|
233
|
+
with( field.to_sym, Range.new(range,range+step) )
|
234
|
+
end
|
235
|
+
end
|
236
|
+
row "Over #{stop}" do
|
237
|
+
# Is greater_than just greater_than or does it also include equal_to?
|
238
|
+
# Results appear to include equal_to which makes it actually incorrect and misleading.
|
239
|
+
with( field.to_sym ).greater_than stop
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end # Sunspot::DSL::Search.class_eval do
|
246
|
+
|
247
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
class ActiveRecordSunspotter::SunspotColumn < OpenStruct
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
# some sensible defaults
|
6
|
+
default_options = {
|
7
|
+
:type => :string,
|
8
|
+
:orderable => true,
|
9
|
+
:facetable => false,
|
10
|
+
:filterable => false,
|
11
|
+
:multiple => false,
|
12
|
+
:default => false
|
13
|
+
}
|
14
|
+
options = args.extract_options!.with_indifferent_access
|
15
|
+
if [String,Symbol].include?( args.first.class ) and !options.has_key?(:name)
|
16
|
+
options[:name] = args.first.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
# if( options[:type] == :null_yndk_string ) && options[:meth].blank?
|
20
|
+
# options[:meth] = ->(s){ YNDK[s.send(:name)]||'NULL' }
|
21
|
+
# options[:type] = :string
|
22
|
+
# end
|
23
|
+
|
24
|
+
default_options.update(options)
|
25
|
+
# default_options[:orderable] = false if options[:type] == :multistring
|
26
|
+
default_options[:orderable] = false if options[:multiple]
|
27
|
+
default_options[:filterable] = true if options[:facetable]
|
28
|
+
super default_options
|
29
|
+
end
|
30
|
+
|
31
|
+
def hash_table
|
32
|
+
instance_variable_get("@table")
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
name
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
__END__
|
43
|
+
|
44
|
+
|
45
|
+
Add :index option. Default to true.
|
46
|
+
False would effectively mean that it is only useful as a column.
|
47
|
+
This may be the same as facetable.
|
48
|
+
WAIT! If aren't indexed, then can't be sorted on.
|
49
|
+
|
50
|
+
So could skip adding if weren't facetable, orderable or multiple.
|
51
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module ActiveRecordSunspotter::SunspotHelper
|
2
|
+
|
3
|
+
# Rails helpers are already "html_safe".
|
4
|
+
# Manually creating the strings will require adding it.
|
5
|
+
|
6
|
+
def operator_radio_button_tag_and_label(name,operator,selected)
|
7
|
+
s = radio_button_tag( "#{name}_op", operator, selected == operator,
|
8
|
+
:id => "#{name}_op_#{operator.downcase}" )
|
9
|
+
s << label_tag( "#{name}_op_#{operator.downcase}", operator )
|
10
|
+
end
|
11
|
+
|
12
|
+
# originally from CLIC
|
13
|
+
|
14
|
+
def multi_select_operator_for(name)
|
15
|
+
content_tag(:div) do
|
16
|
+
selected = ( params["#{name}_op"] && params["#{name}_op"] == 'AND' ) ? 'AND' : 'OR'
|
17
|
+
s = content_tag(:span,"Multi-select operator")
|
18
|
+
s << operator_radio_button_tag_and_label(name,'AND',selected)
|
19
|
+
s << operator_radio_button_tag_and_label(name,'OR',selected)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def facet_toggle(facet,icon)
|
24
|
+
content_tag(:div,:class => 'facet_toggle') do
|
25
|
+
s = content_tag(:span,' '.html_safe,:class => "ui-icon #{icon}")
|
26
|
+
# Don't include the blank fields, so don't count them.
|
27
|
+
# May need to figure out how to deal with blanks in the future
|
28
|
+
# as occassionally they are what one would be searching for.
|
29
|
+
#
|
30
|
+
# 20130423 - false.blank? is true so boolean fields won't work here
|
31
|
+
#
|
32
|
+
# perhaps do r.value.to_s.blank? as 'false'.blank? is false
|
33
|
+
#
|
34
|
+
non_blank_row_count = facet.rows.reject{|r|r.value.blank?}.length
|
35
|
+
facet_label = facet.name.to_s
|
36
|
+
facet_label = if( facet_label.match(/^hex_/) )
|
37
|
+
# [facet_label.gsub(/^hex_/,'').split(/:/).first].pack('H*')
|
38
|
+
l = facet_label.gsub(/^hex_/,'').split(/:/)
|
39
|
+
l[0] = [l[0]].pack('H*')
|
40
|
+
l.join(' : ')
|
41
|
+
else
|
42
|
+
column = @sunspot_search_class.all_sunspot_columns.detect{|c|c.name == facet_label}
|
43
|
+
|
44
|
+
# Check if a translation exists. Use it if it does, otherwise ...
|
45
|
+
# http://stackoverflow.com/questions/12353416/rails-i18n-check-if-translation-exists
|
46
|
+
column.label || ( I18n.t("#{@sunspot_search_class.to_s.underscore}.#{facet.name}",
|
47
|
+
:scope => "activerecord.attributes",:raise => true ) rescue false ) || facet_label.titleize
|
48
|
+
end
|
49
|
+
s << link_to("#{facet_label} (#{non_blank_row_count})".html_safe, 'javascript:void()')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def facet_for(facet,options={})
|
54
|
+
return if facet.rows.empty?
|
55
|
+
|
56
|
+
# options include :multiselector, :facetcount
|
57
|
+
style, icon = if( params[facet.name] )
|
58
|
+
[" style='display:block;'", "ui-icon-triangle-1-s"]
|
59
|
+
else
|
60
|
+
[ nil, "ui-icon-triangle-1-e"]
|
61
|
+
end
|
62
|
+
s = facet_toggle(facet,icon)
|
63
|
+
|
64
|
+
# Wrap all the rest in a div which will be toggled
|
65
|
+
s << "\n<div id='#{facet.name}' class='facet_field'#{style}>\n".html_safe
|
66
|
+
s << multi_select_operator_for(facet.name) if options[:multiselector]
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
col = @sunspot_search_class.sunspot_columns.detect{|c|
|
71
|
+
c.name == facet.name.to_s }
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
s << "<ul class='facet_field_values'>\n".html_safe
|
76
|
+
facet.rows.each do |row|
|
77
|
+
|
78
|
+
#
|
79
|
+
# NOTE for now, if a blank field has made it into the index, IGNORE IT.
|
80
|
+
# Unfortunately, searching for a '' creates syntactically incorrect query.
|
81
|
+
#
|
82
|
+
# Of course, this mucks up the count. Errr!!!
|
83
|
+
# So I had to handle it yet again.
|
84
|
+
#
|
85
|
+
|
86
|
+
#
|
87
|
+
# 20130423 - false.blank? is true so boolean fields won't work here
|
88
|
+
#
|
89
|
+
next if row.value.blank?
|
90
|
+
|
91
|
+
label = if col.ranges
|
92
|
+
col.ranges.detect{|r|
|
93
|
+
r[:range].to_s == row.value.to_s }[:name] || 'RANGE NOT FOUND'
|
94
|
+
else
|
95
|
+
row.value
|
96
|
+
end
|
97
|
+
|
98
|
+
# TODO figure out how to facet on NULL and BLANK values
|
99
|
+
# I don't think that NULL gets faceted
|
100
|
+
# and blank creates a syntactically incorrect query
|
101
|
+
|
102
|
+
s << "<li>".html_safe
|
103
|
+
if options[:radio]
|
104
|
+
s << radio_button_tag( "#{facet.name}[]", row.value,
|
105
|
+
[params[facet.name]].flatten.include?(row.value.to_s),
|
106
|
+
{ :id => "#{facet.name}_#{row.value.html_friendly}" } )
|
107
|
+
else
|
108
|
+
s << check_box_tag( "#{facet.name}[]", row.value,
|
109
|
+
[params[facet.name]].flatten.include?(row.value.to_s),
|
110
|
+
{ :id => "#{facet.name}_#{row.value.html_friendly}" } )
|
111
|
+
end
|
112
|
+
s << "<label for='#{facet.name}_#{row.value.html_friendly}'>".html_safe
|
113
|
+
# s << "<span>#{row.value}</span>".html_safe
|
114
|
+
s << "<span>#{label}</span>".html_safe
|
115
|
+
s << " ( #{row.count} )".html_safe if options[:facet_counts]
|
116
|
+
s << "</label></li>\n".html_safe
|
117
|
+
end
|
118
|
+
s << "</ul>\n".html_safe # "<ul class='facet_field_values'>"
|
119
|
+
s << "</div>\n".html_safe # "<div id='#{facet.name}' class='facet_field'#{style}>"
|
120
|
+
end
|
121
|
+
|
122
|
+
def columns
|
123
|
+
columns ||= if( params[:c].present? )
|
124
|
+
[params[:c]].flatten.uniq # sometimes this is needed and others it is not?
|
125
|
+
else
|
126
|
+
@sunspot_search_class.sunspot_default_column_names
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def column_header(column)
|
131
|
+
if @sunspot_search_class.sunspot_orderable_column_names.include?(column.to_s)
|
132
|
+
sort_link(column,:image => false)
|
133
|
+
else
|
134
|
+
column
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# NEVER use send on a tainted string!
|
140
|
+
#
|
141
|
+
def column_content(subject,column)
|
142
|
+
case column.to_s
|
143
|
+
|
144
|
+
#
|
145
|
+
# Just wanting to format any date columns
|
146
|
+
#
|
147
|
+
when *@sunspot_search_class.sunspot_date_columns.collect(&:name)
|
148
|
+
col = @sunspot_search_class.sunspot_columns.detect{|c|
|
149
|
+
c.name == column.to_s }
|
150
|
+
( col.hash_table.has_key?(:meth) ) ?
|
151
|
+
[col.meth.call(subject)].flatten.join(',') :
|
152
|
+
( subject.respond_to?(column) ) ?
|
153
|
+
subject.try(column).try(:strftime,'%m/%d/%Y') :
|
154
|
+
'DATE COLUMN NOT FOUND?'
|
155
|
+
|
156
|
+
#
|
157
|
+
# All valid columns can use meth, so I don't need to define them.
|
158
|
+
#
|
159
|
+
when *@sunspot_search_class.sunspot_column_names
|
160
|
+
col = @sunspot_search_class.sunspot_columns.detect{|c|
|
161
|
+
c.name == column.to_s }
|
162
|
+
( col.hash_table.has_key?(:meth) ) ?
|
163
|
+
[col.meth.call(subject)].flatten.join(',') :
|
164
|
+
( subject.respond_to?(column) ) ?
|
165
|
+
subject.try(column) :
|
166
|
+
'COLUMN NOT FOUND?'
|
167
|
+
|
168
|
+
else
|
169
|
+
'UNKNOWN COLUMN'
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
def html_column_content(result,column)
|
177
|
+
content = column_content(result,column).to_s.gsub(/\s+/,' ') || ' '
|
178
|
+
|
179
|
+
col = @sunspot_search_class.sunspot_columns.detect{|c|
|
180
|
+
c.name == column.to_s }
|
181
|
+
html_content = ( col.hash_table.has_key?(:link_to) ) ?
|
182
|
+
link_to( content, col.link_to.call(result), :target => :new ) : content
|
183
|
+
|
184
|
+
html_content.html_safe
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Sunspot::Rails::Server
|
2
|
+
# http://opensoul.org/blog/archives/2010/04/07/cucumber-and-sunspot/
|
3
|
+
def running?
|
4
|
+
begin
|
5
|
+
open("http://localhost:#{self.port}/")
|
6
|
+
true
|
7
|
+
rescue Errno::ECONNREFUSED => e
|
8
|
+
# server not running yet
|
9
|
+
false
|
10
|
+
rescue OpenURI::HTTPError
|
11
|
+
# getting a response so the server is running
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ActiveRecordSunspotter::Sunspotability
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
base.class_eval do
|
5
|
+
|
6
|
+
#
|
7
|
+
# MUST delay execution of code until included as cattr_accessor is ActiveRecord specific
|
8
|
+
#
|
9
|
+
cattr_accessor :all_sunspot_columns
|
10
|
+
self.all_sunspot_columns = [] # order is only relevant to the facets
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def add_sunspot_column(*args)
|
17
|
+
all_sunspot_columns.push( ActiveRecordSunspotter::SunspotColumn.new( *args ) )
|
18
|
+
end
|
19
|
+
|
20
|
+
def sunspot_orderable_columns
|
21
|
+
all_sunspot_columns.select{|c|c.orderable}
|
22
|
+
end
|
23
|
+
|
24
|
+
def sunspot_orderable_column_names
|
25
|
+
sunspot_orderable_columns.collect(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def sunspot_default_columns
|
29
|
+
all_sunspot_columns.select{|c|c.default}
|
30
|
+
end
|
31
|
+
|
32
|
+
def sunspot_default_column_names
|
33
|
+
sunspot_default_columns.collect(&:name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def sunspot_all_filters
|
37
|
+
all_sunspot_columns.select{|c|c.filterable}
|
38
|
+
end
|
39
|
+
|
40
|
+
# in the order that they will appear on the page
|
41
|
+
def sunspot_all_facets
|
42
|
+
all_sunspot_columns.select{|c|c.facetable}
|
43
|
+
end
|
44
|
+
def sunspot_all_facet_names
|
45
|
+
sunspot_all_facets.collect(&:name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def sunspot_columns
|
49
|
+
all_sunspot_columns
|
50
|
+
end
|
51
|
+
|
52
|
+
def sunspot_column_names
|
53
|
+
all_sunspot_columns.collect(&:name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def sunspot_available_column_names
|
57
|
+
sunspot_column_names.sort
|
58
|
+
end
|
59
|
+
|
60
|
+
def sunspot_date_columns
|
61
|
+
all_sunspot_columns.select{|c|c.type == :date }
|
62
|
+
end
|
63
|
+
|
64
|
+
def searchable_plus(&block)
|
65
|
+
searchable do
|
66
|
+
#
|
67
|
+
# Trying to simplify. Simplify? Minimize?
|
68
|
+
# Will I need all of the above methods after this is done?
|
69
|
+
#
|
70
|
+
# .select{|c| c.facetable }
|
71
|
+
# or just use "sunspot_all_facets" instead of "all_sunspot_columns"
|
72
|
+
# WAIT! If aren't indexed, then can't be sorted on.
|
73
|
+
all_sunspot_columns.select{|c| ![:boolean,:nulled_string].include?(c.type) }.each{|c|
|
74
|
+
options = {}
|
75
|
+
options[:multiple] = true if( c.multiple )
|
76
|
+
#
|
77
|
+
# I don't think that trie works with :long or :double
|
78
|
+
# I got this when I tried a :double
|
79
|
+
# Trie fields are only valid for numeric and time types
|
80
|
+
#
|
81
|
+
# I found documentation that says that Lucene/Solr should work for longs and doubles?
|
82
|
+
#
|
83
|
+
#
|
84
|
+
# these are missing in sunspot 2.0. I added them in my taxonomy app
|
85
|
+
#
|
86
|
+
# module Sunspot::Type
|
87
|
+
# class TrieDoubleType < DoubleType
|
88
|
+
# def indexed_name(name)
|
89
|
+
# "#{super}t"
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
# class TrieLongType < LongType
|
93
|
+
# def indexed_name(name)
|
94
|
+
# "#{super}t"
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# options[:trie] = true if( [:integer,:long,:double,:float,:time].include?(c.type) )
|
100
|
+
options[:trie] = true if( [:integer,:float,:time].include?(c.type) )
|
101
|
+
send( c.type, c.name, options ){
|
102
|
+
c.hash_table.has_key?(:meth) ? c.meth.call(self) : send( c.name )
|
103
|
+
}
|
104
|
+
#
|
105
|
+
# booleans? nulled_strings?
|
106
|
+
#
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
# yield if block_given?
|
111
|
+
# yield block if block_given?
|
112
|
+
end
|
113
|
+
|
114
|
+
# this works, but why can't I just yield inside the block
|
115
|
+
searchable &block if block_given?
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end # module ClassMethods
|
120
|
+
|
121
|
+
end
|
122
|
+
__END__
|
123
|
+
|
124
|
+
What's the point of adding a column to the index that isn't faceted?
|
125
|
+
It would basically just be useful as a column, which is why I was
|
126
|
+
considering creating an "index" option.
|
127
|
+
WAIT! If aren't indexed, then can't be sorted on.
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_record_sunspotter'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
jQuery(function(){
|
2
|
+
|
3
|
+
jQuery('div.facet_toggle a').click(function(){
|
4
|
+
// jQuery(this).parent().next().toggle(500);
|
5
|
+
// added 'blind' so doesn't resize stuff and just slides in.
|
6
|
+
// be advised that this effect temporarily wraps the target in a div until done.
|
7
|
+
jQuery(this).parent().next().toggle('blind',500);
|
8
|
+
jQuery(this).prev().toggleClass('ui-icon-triangle-1-e');
|
9
|
+
jQuery(this).prev().toggleClass('ui-icon-triangle-1-s');
|
10
|
+
return false;
|
11
|
+
});
|
12
|
+
|
13
|
+
jQuery( "#selected_columns, #unselected_columns" ).sortable({
|
14
|
+
connectWith: ".selectable_columns"
|
15
|
+
}).disableSelection();
|
16
|
+
|
17
|
+
jQuery('form').submit(function(){
|
18
|
+
jQuery('#selected_columns li').each(function(){
|
19
|
+
jQuery('<input>').attr({
|
20
|
+
name: 'c[]',
|
21
|
+
type: 'hidden',
|
22
|
+
value: $(this).text()
|
23
|
+
}).appendTo('form');
|
24
|
+
});
|
25
|
+
});
|
26
|
+
|
27
|
+
});
|
@@ -0,0 +1,207 @@
|
|
1
|
+
body {
|
2
|
+
min-width: 100%;
|
3
|
+
padding:0;
|
4
|
+
margin:0;
|
5
|
+
}
|
6
|
+
div#header {
|
7
|
+
margin:0;
|
8
|
+
padding:0;
|
9
|
+
background-color: #6A7780;
|
10
|
+
height: auto;
|
11
|
+
div#header_top {
|
12
|
+
height: 50px;
|
13
|
+
display:block;
|
14
|
+
padding: 0px;
|
15
|
+
margin: 0px;
|
16
|
+
border: 0px;
|
17
|
+
clear:both;
|
18
|
+
}
|
19
|
+
}
|
20
|
+
div#logo {
|
21
|
+
float:left;
|
22
|
+
padding-left: 10px;
|
23
|
+
font-family: Times New Roman,LeagueGothicRegular,helvetica,arial,sans-serif;
|
24
|
+
h2 {
|
25
|
+
font-size: 30px;
|
26
|
+
margin: 10px;
|
27
|
+
margin-top: 3px;
|
28
|
+
margin-bottom: 0px;
|
29
|
+
color:white;
|
30
|
+
text-shadow: #000 0 2px 3px;
|
31
|
+
display:inline-block;
|
32
|
+
}
|
33
|
+
a {
|
34
|
+
color:white;
|
35
|
+
text-decoration:none;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
#container {
|
39
|
+
padding: 0;
|
40
|
+
margin: auto 0px;
|
41
|
+
display:table;
|
42
|
+
// width: 100%;
|
43
|
+
min-width: 100%;
|
44
|
+
}
|
45
|
+
|
46
|
+
#footer {
|
47
|
+
clear:both;
|
48
|
+
padding-top: 1ex;
|
49
|
+
padding-bottom: 1ex;
|
50
|
+
text-align:center;
|
51
|
+
font-size:8pt;
|
52
|
+
line-height:8pt;
|
53
|
+
background-color: #EEE;
|
54
|
+
color: gray;
|
55
|
+
}
|
56
|
+
|
57
|
+
|
58
|
+
.table {
|
59
|
+
/* this holds the table-row open, otherwise shrinks to whatever */
|
60
|
+
display:table;
|
61
|
+
width:100%;
|
62
|
+
.row {
|
63
|
+
display:table-row;
|
64
|
+
> div {
|
65
|
+
display:table-cell;
|
66
|
+
vertical-align: top;
|
67
|
+
text-align: center;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
table {
|
72
|
+
width: 100%;
|
73
|
+
}
|
74
|
+
|
75
|
+
tr:nth-child(2n) { background-color: #FCF9EF; }
|
76
|
+
tr:nth-child(2n+1) { background-color: #F5F1E8; }
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
#refine {
|
89
|
+
min-width: 300px;
|
90
|
+
/* still don't understand why, but when min-width is used, the facet column flickers too wide for a second when toggling? */
|
91
|
+
/* interestingly, having both width and min-width seems to do exactly what I want without flicker. Yay */
|
92
|
+
width: 400px;
|
93
|
+
border-right: 2px solid silver;
|
94
|
+
padding: 0 10px;
|
95
|
+
form input[type=submit] {
|
96
|
+
font-size:14pt;
|
97
|
+
font-weight:bold;
|
98
|
+
border: 1px solid black;
|
99
|
+
width: 100%;
|
100
|
+
margin: 10px 0;
|
101
|
+
|
102
|
+
/* http://css-radius.heroku.com */
|
103
|
+
-webkit-border-radius: 8px;
|
104
|
+
-moz-border-radius: 8px;
|
105
|
+
border-radius: 8px;
|
106
|
+
|
107
|
+
background-color: #DDDDDD;
|
108
|
+
/* for webkit browsers */
|
109
|
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#FFFFFF), to(#BBBBBB));
|
110
|
+
/* for firefox 3.6+ */
|
111
|
+
background-image: -moz-linear-gradient(top, #FFFFFF, #BBBBBB);
|
112
|
+
/* for ie , but doesn't seem to work as expected despite other examples working */
|
113
|
+
filter: progid:DXImageTransform.Microsoft.gradient(StartColorStr='#FFFFFF', EndColorStr='#BBBBBB');
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
.facets {
|
120
|
+
border: 1px solid silver;
|
121
|
+
border-top: 0;
|
122
|
+
div.facet_toggle {
|
123
|
+
display:block;
|
124
|
+
padding: 5px;
|
125
|
+
border-top: 1px solid silver;
|
126
|
+
background-color:#EEE;
|
127
|
+
text-align: left;
|
128
|
+
* {
|
129
|
+
vertical-align:middle;
|
130
|
+
}
|
131
|
+
span {
|
132
|
+
display: inline-block;
|
133
|
+
padding-right:5px;
|
134
|
+
}
|
135
|
+
a {
|
136
|
+
display:inline-block;
|
137
|
+
text-align: left;
|
138
|
+
}
|
139
|
+
}
|
140
|
+
div.facet_field {
|
141
|
+
display:none;
|
142
|
+
ul {
|
143
|
+
padding-left: 35px;
|
144
|
+
/* I want a 10px padding, but also want the wordwrap indented
|
145
|
+
further. so large padding and negative indent */
|
146
|
+
li {
|
147
|
+
list-style: none;
|
148
|
+
text-align: left;
|
149
|
+
text-indent: -30px;
|
150
|
+
label {
|
151
|
+
padding-left: 5px;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
#results {
|
159
|
+
padding: 0 10px;
|
160
|
+
background-color: #FFF;
|
161
|
+
}
|
162
|
+
|
163
|
+
#filters {
|
164
|
+
padding: 0 10px;
|
165
|
+
border: 1px solid gray;
|
166
|
+
h3 {
|
167
|
+
margin-top:0;
|
168
|
+
padding-top:10px;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
ul.selectable_columns {
|
175
|
+
background-color: silver;
|
176
|
+
padding: 10px;
|
177
|
+
list-style: none;
|
178
|
+
text-align: left;
|
179
|
+
text-indent: 20px;
|
180
|
+
}
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
/* only used in ODMS so should probably move it there */
|
186
|
+
div#map_canvas_background {
|
187
|
+
position: absolute;
|
188
|
+
width: 100%;
|
189
|
+
height: 100%;
|
190
|
+
background-color: gray;
|
191
|
+
opacity: 0.6;
|
192
|
+
filter:alpha(opacity=60);
|
193
|
+
top: 0px;
|
194
|
+
}
|
195
|
+
div#map_canvas_wrapper {
|
196
|
+
position: absolute;
|
197
|
+
width: 100%;
|
198
|
+
height: 100%;
|
199
|
+
margin: auto;
|
200
|
+
text-align: center;
|
201
|
+
top: 0px;
|
202
|
+
}
|
203
|
+
div#map_canvas {
|
204
|
+
width: 90%;
|
205
|
+
height: 90%;
|
206
|
+
margin: 10px auto;
|
207
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<head>
|
2
|
+
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW" />
|
3
|
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
4
|
+
<%# explicity adding root_url to href making an absolute path
|
5
|
+
actually allows the cal favicon to load %>
|
6
|
+
<link rel="shortcut icon" href="<%=root_url%><%=image_path('favicon.ico')%>" />
|
7
|
+
<title><%= @page_title || "Sunspotter" -%></title>
|
8
|
+
<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/themes/base/jquery-ui.css" media="screen" rel="stylesheet" type="text/css" />
|
9
|
+
<%= stylesheet_link_tag 'sunspot' %>
|
10
|
+
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js" type="text/javascript"></script>
|
11
|
+
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js" type="text/javascript"></script>
|
12
|
+
<%= javascript_include_tag 'application','sunspot' %>
|
13
|
+
<%= yield :head %>
|
14
|
+
</head>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
<%#= yield :doctype -%>
|
4
|
+
<%#
|
5
|
+
Could add "valid" tag attributes by ...
|
6
|
+
[
|
7
|
+
<!ATTLIST tag myAttri CDATA #IMPLIED>
|
8
|
+
] %>
|
9
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
10
|
+
<%= render 'layouts/sunspot_head' %>
|
11
|
+
<body>
|
12
|
+
<div id='container'>
|
13
|
+
<%= render 'layouts/sunspot_header' %>
|
14
|
+
<div class='table'>
|
15
|
+
<div class='row'>
|
16
|
+
<%= yield %>
|
17
|
+
</div><!-- row -->
|
18
|
+
</div><!-- table -->
|
19
|
+
<%= render 'layouts/footer' %>
|
20
|
+
</div><!-- container -->
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class='facets'>
|
2
|
+
<% @search.facets.each do |facet| -%>
|
3
|
+
<% next if facet.rows.empty? -%><%# don't show empty facets -%>
|
4
|
+
<%= facet_for(facet,
|
5
|
+
:multiselector => @sunspot_search_class.sunspot_columns.detect{|c|
|
6
|
+
c.name == facet.name.to_s }.try(:multiple),
|
7
|
+
:facet_counts => true ) %>
|
8
|
+
<%#
|
9
|
+
multiselector is for when a single subject could have many
|
10
|
+
Either as an array in itself or perhaps some implementation
|
11
|
+
of a has_many for enrollments or something
|
12
|
+
%>
|
13
|
+
<% end -%><%# @search.facets.each do |facet| -%>
|
14
|
+
<%= render 'select_columns' %>
|
15
|
+
</div><!-- class='facets' -->
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<% @sunspot_search_class.sunspot_all_filters.each do |f| %>
|
2
|
+
<% unless f.facetable %>
|
3
|
+
<% if params["#{f.name}_op"].present? %>
|
4
|
+
<%= hidden_field_tag "#{f.name}_op", params["#{f.name}_op"], :id => nil %>
|
5
|
+
<% end %>
|
6
|
+
<% if params[f.name].present? %>
|
7
|
+
<% if params[f.name].is_a?(Array) %>
|
8
|
+
<% params[f.name].each do |value| %>
|
9
|
+
<%= hidden_field_tag "#{f.name}[]", value, :id => nil %>
|
10
|
+
<% end %>
|
11
|
+
<% else %>
|
12
|
+
|
13
|
+
<% end %>
|
14
|
+
<% end %><%# if params[f.name].present? %>
|
15
|
+
<% end %><%# unless f.facetable %>
|
16
|
+
<% end %><%# @sunspot_search_class.sunspot_all_filters.each do |f| %>
|
@@ -0,0 +1 @@
|
|
1
|
+
LINK
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<div id='results'>
|
2
|
+
<% unless @search.results.empty? %>
|
3
|
+
<table><thead><tr>
|
4
|
+
<th> </th>
|
5
|
+
<% columns.each do |column| %>
|
6
|
+
<th><%= column_header(column) %></th>
|
7
|
+
<% end %>
|
8
|
+
</tr></thead><tbody>
|
9
|
+
<% @search.results.each do |result| %>
|
10
|
+
<tr class='row'>
|
11
|
+
<td><%= render 'link', :result => result %></td>
|
12
|
+
<% columns.each do |column| %>
|
13
|
+
<td><%#= column_content(result,column).to_s.gsub(/\s+/,' ').html_safe || ' '.html_safe -%><%= html_column_content(result,column) %></td>
|
14
|
+
<% end %>
|
15
|
+
</tr>
|
16
|
+
<% end %>
|
17
|
+
</tbody></table>
|
18
|
+
<br/><%= will_paginate(@search.hits) %>
|
19
|
+
<p class='page_info'>Displaying page <%=@search.hits.current_page%> of
|
20
|
+
<%=@search.hits.total_pages%> out of <%=@search.total%> results</p>
|
21
|
+
<% else %>
|
22
|
+
<p>No results</p>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<div class="facet_toggle">
|
2
|
+
<span class="ui-icon ui-icon-triangle-1-e"> </span><a href="javascript:void(0)">Column Selection</a>
|
3
|
+
</div>
|
4
|
+
<div id='columns' class='facet_field'>
|
5
|
+
<p>Used Columns</p>
|
6
|
+
<ul id="selected_columns" class="selectable_columns">
|
7
|
+
<% columns.each do |column| %>
|
8
|
+
<%# NEED to find a way to use the id, but already using id matching these
|
9
|
+
column names in the facet selectors %>
|
10
|
+
<%= content_tag(:li, column )%>
|
11
|
+
<% end %>
|
12
|
+
</ul>
|
13
|
+
<p>Unused Columns</p>
|
14
|
+
<ul id="unselected_columns" class="selectable_columns">
|
15
|
+
<% (@sunspot_search_class.sunspot_available_column_names - columns).each do |column| %>
|
16
|
+
<%# NEED to find a way to use the id, but already using id matching these
|
17
|
+
column names in the facet selectors %>
|
18
|
+
<%= content_tag(:li, column )%>
|
19
|
+
<% end %>
|
20
|
+
</ul>
|
21
|
+
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<% require 'csv' -%>
|
2
|
+
<%#
|
3
|
+
adding :headers => true is supposed to set a flag in the file
|
4
|
+
somewhere marking it as having a header row, but I don't
|
5
|
+
think that it actually does.
|
6
|
+
|
7
|
+
also, use a - with the ruby closing tag to avoid extra lines in csv file.
|
8
|
+
|
9
|
+
Unfortunately, when Access imports a csv file it adds a type to columns, I think.
|
10
|
+
|
11
|
+
The hospital_no is treated like an integer. If it gets too big or contains
|
12
|
+
a non-digit character, it just leaves the field blank. This is an absolutely
|
13
|
+
awful idea. Thanks again Microsoft. Because of this, I force quotes.
|
14
|
+
I really only need to quote the hospital_no, but it is easier to just quote them all.
|
15
|
+
|
16
|
+
-%>
|
17
|
+
<%= columns.to_csv( :headers => true) -%>
|
18
|
+
<% @search.results.each do |result| %>
|
19
|
+
<%= columns.collect{|c| column_content(result,c) }.to_csv(:force_quotes => true).html_safe -%>
|
20
|
+
<% end -%>
|
21
|
+
<%#
|
22
|
+
|
23
|
+
I added html_safe to stop any html encoding just in case
|
24
|
+
|
25
|
+
-%>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<%# require 'csv' -%>
|
2
|
+
<%#
|
3
|
+
adding :headers => true is supposed to set a flag in the file
|
4
|
+
somewhere marking it as having a header row, but I don't
|
5
|
+
think that it actually does.
|
6
|
+
|
7
|
+
also, use a - with the ruby closing tag to avoid extra lines in csv file.
|
8
|
+
|
9
|
+
Unfortunately, when Access imports a csv file it adds a type to columns, I think.
|
10
|
+
|
11
|
+
The hospital_no is treated like an integer. If it gets too big or contains
|
12
|
+
a non-digit character, it just leaves the field blank. This is an absolutely
|
13
|
+
awful idea. Thanks again Microsoft. Because of this, I force quotes.
|
14
|
+
I really only need to quote the hospital_no, but it is easier to just quote them all.
|
15
|
+
|
16
|
+
-%>
|
17
|
+
<%#= columns.to_csv( :headers => true) -%>
|
18
|
+
<%= @search.results.collect do |result| -%>
|
19
|
+
<% columns.inject({}){|h,c| h[c] = column_content(result,c); h} -%>
|
20
|
+
<% end.to_json.html_safe -%>
|
21
|
+
<%#
|
22
|
+
|
23
|
+
I added html_safe to stop any html encoding just in case
|
24
|
+
|
25
|
+
-%>
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jakewendt-active_record_sunspotter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.12
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- George 'Jake' Wendt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sunspot_rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sunspot_solr
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: progress_bar
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: active_record_sunspotter
|
56
|
+
email: github@jakewendt.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README.rdoc
|
61
|
+
files:
|
62
|
+
- lib/active_record_sunspotter.rb
|
63
|
+
- lib/active_record_sunspotter/rails/engine.rb
|
64
|
+
- lib/active_record_sunspotter/rails/railtie.rb
|
65
|
+
- lib/active_record_sunspotter/search_sunspot_for.rb
|
66
|
+
- lib/active_record_sunspotter/sunspot_column.rb
|
67
|
+
- lib/active_record_sunspotter/sunspot_helper.rb
|
68
|
+
- lib/active_record_sunspotter/sunspot_rails_server.rb
|
69
|
+
- lib/active_record_sunspotter/sunspotability.rb
|
70
|
+
- lib/jakewendt-active_record_sunspotter.rb
|
71
|
+
- vendor/assets/javascripts/sunspot.js
|
72
|
+
- vendor/assets/stylesheets/sunspot.css.scss
|
73
|
+
- vendor/views/layouts/_footer.html.erb
|
74
|
+
- vendor/views/layouts/_sunspot_head.html.erb
|
75
|
+
- vendor/views/layouts/_sunspot_header.html.erb
|
76
|
+
- vendor/views/layouts/sunspot.html.erb
|
77
|
+
- vendor/views/sunspot/_download_csv.html.erb
|
78
|
+
- vendor/views/sunspot/_facets.html.erb
|
79
|
+
- vendor/views/sunspot/_filters.html.erb
|
80
|
+
- vendor/views/sunspot/_link.html.erb
|
81
|
+
- vendor/views/sunspot/_per_page.html.erb
|
82
|
+
- vendor/views/sunspot/_results.html.erb
|
83
|
+
- vendor/views/sunspot/_select_columns.html.erb
|
84
|
+
- vendor/views/sunspot/index.csv.erb
|
85
|
+
- vendor/views/sunspot/index.json.erb
|
86
|
+
- README.rdoc
|
87
|
+
homepage: http://github.com/jakewendt/active_record_sunspotter
|
88
|
+
licenses: []
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.0.14
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: active_record_sunspotter
|
110
|
+
test_files: []
|