jakewendt-active_record_sunspotter 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|