outoftime-sunspot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/History.txt +7 -0
  2. data/Manifest.txt +104 -0
  3. data/PostInstall.txt +7 -0
  4. data/README.rdoc +115 -0
  5. data/Rakefile +27 -0
  6. data/config/hoe.rb +74 -0
  7. data/config/requirements.rb +15 -0
  8. data/lib/light_config.rb +40 -0
  9. data/lib/sunspot/adapters.rb +54 -0
  10. data/lib/sunspot/conditions.rb +50 -0
  11. data/lib/sunspot/conditions_builder.rb +37 -0
  12. data/lib/sunspot/configuration.rb +17 -0
  13. data/lib/sunspot/field.rb +108 -0
  14. data/lib/sunspot/field_builder.rb +37 -0
  15. data/lib/sunspot/indexer.rb +65 -0
  16. data/lib/sunspot/query.rb +115 -0
  17. data/lib/sunspot/query_builder.rb +30 -0
  18. data/lib/sunspot/restriction.rb +72 -0
  19. data/lib/sunspot/scope_builder.rb +33 -0
  20. data/lib/sunspot/search.rb +83 -0
  21. data/lib/sunspot/session.rb +43 -0
  22. data/lib/sunspot/type.rb +62 -0
  23. data/lib/sunspot/util.rb +13 -0
  24. data/lib/sunspot/version.rb +10 -0
  25. data/lib/sunspot.rb +50 -0
  26. data/setup.rb +1585 -0
  27. data/solr/README.txt +36 -0
  28. data/solr/etc/jetty.xml +206 -0
  29. data/solr/etc/webdefault.xml +379 -0
  30. data/solr/exampledocs/books.csv +11 -0
  31. data/solr/exampledocs/hd.xml +46 -0
  32. data/solr/exampledocs/ipod_other.xml +50 -0
  33. data/solr/exampledocs/ipod_video.xml +35 -0
  34. data/solr/exampledocs/mem.xml +58 -0
  35. data/solr/exampledocs/monitor.xml +31 -0
  36. data/solr/exampledocs/monitor2.xml +30 -0
  37. data/solr/exampledocs/mp500.xml +39 -0
  38. data/solr/exampledocs/post.jar +0 -0
  39. data/solr/exampledocs/post.sh +28 -0
  40. data/solr/exampledocs/sd500.xml +33 -0
  41. data/solr/exampledocs/solr.xml +38 -0
  42. data/solr/exampledocs/spellchecker.xml +58 -0
  43. data/solr/exampledocs/utf8-example.xml +42 -0
  44. data/solr/exampledocs/vidcard.xml +52 -0
  45. data/solr/lib/jetty-6.1.3.jar +0 -0
  46. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  47. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  48. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  49. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  50. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  51. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  52. data/solr/solr/README.txt +52 -0
  53. data/solr/solr/bin/abc +176 -0
  54. data/solr/solr/bin/abo +176 -0
  55. data/solr/solr/bin/backup +108 -0
  56. data/solr/solr/bin/backupcleaner +142 -0
  57. data/solr/solr/bin/commit +128 -0
  58. data/solr/solr/bin/optimize +129 -0
  59. data/solr/solr/bin/readercycle +129 -0
  60. data/solr/solr/bin/rsyncd-disable +77 -0
  61. data/solr/solr/bin/rsyncd-enable +76 -0
  62. data/solr/solr/bin/rsyncd-start +145 -0
  63. data/solr/solr/bin/rsyncd-stop +105 -0
  64. data/solr/solr/bin/scripts-util +83 -0
  65. data/solr/solr/bin/snapcleaner +148 -0
  66. data/solr/solr/bin/snapinstaller +168 -0
  67. data/solr/solr/bin/snappuller +248 -0
  68. data/solr/solr/bin/snappuller-disable +77 -0
  69. data/solr/solr/bin/snappuller-enable +77 -0
  70. data/solr/solr/bin/snapshooter +109 -0
  71. data/solr/solr/conf/admin-extra.html +31 -0
  72. data/solr/solr/conf/protwords.txt +21 -0
  73. data/solr/solr/conf/schema.xml +231 -0
  74. data/solr/solr/conf/scripts.conf +24 -0
  75. data/solr/solr/conf/solrconfig.xml +394 -0
  76. data/solr/solr/conf/stopwords.txt +57 -0
  77. data/solr/solr/conf/synonyms.txt +31 -0
  78. data/solr/start.jar +0 -0
  79. data/solr/webapps/solr.war +0 -0
  80. data/tasks/deployment.rake +34 -0
  81. data/tasks/environment.rake +7 -0
  82. data/tasks/rcov.rake +6 -0
  83. data/tasks/solr.rake +12 -0
  84. data/tasks/website.rake +17 -0
  85. data/test/api/test_build_search.rb +216 -0
  86. data/test/api/test_helper.rb +4 -0
  87. data/test/api/test_indexer.rb +110 -0
  88. data/test/api/test_retrieve_search.rb +114 -0
  89. data/test/api/test_session.rb +46 -0
  90. data/test/custom_expectation.rb +53 -0
  91. data/test/integration/test_field_types.rb +62 -0
  92. data/test/integration/test_helper.rb +1 -0
  93. data/test/integration/test_keyword_search.rb +32 -0
  94. data/test/integration/test_pagination.rb +32 -0
  95. data/test/mocks/base_class.rb +2 -0
  96. data/test/mocks/comment.rb +28 -0
  97. data/test/mocks/mock_adapter.rb +25 -0
  98. data/test/mocks/post.rb +41 -0
  99. data/test/test_helper.rb +31 -0
  100. metadata +191 -0
@@ -0,0 +1,37 @@
1
+ module Sunspot
2
+ class FieldBuilder
3
+ def initialize(clazz)
4
+ @clazz = clazz
5
+ end
6
+
7
+ def text(*names, &block)
8
+ for name in names
9
+ ::Sunspot::Field.register_text clazz, build_field(name, ::Sunspot::Type::TextType, &block)
10
+ end
11
+ end
12
+
13
+ def method_missing(method, *args, &block)
14
+ begin
15
+ type = ::Sunspot::Type.const_get "#{method.to_s.camel_case}Type"
16
+ rescue(NameError)
17
+ super(method.to_sym, *args, &block) and return
18
+ end
19
+ name = args.shift
20
+ ::Sunspot::Field.register clazz, build_field(name, type, *args, &block)
21
+ end
22
+
23
+ protected
24
+ attr_reader :clazz
25
+
26
+ private
27
+
28
+ def build_field(name, type, *args, &block)
29
+ options = args.shift if args.first.is_a?(Hash)
30
+ unless block
31
+ ::Sunspot::Field::AttributeField.new(name, type, options || {})
32
+ else
33
+ ::Sunspot::Field::VirtualField.new(name, type, options || {}, &block)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,65 @@
1
+ module Sunspot
2
+ class Indexer
3
+ def initialize(connection)
4
+ @connection = connection
5
+ end
6
+
7
+ def add(model)
8
+ hash = static_hash_for model
9
+ for field in fields
10
+ hash.merge! field.pair_for(model)
11
+ end
12
+ connection.add hash
13
+ end
14
+
15
+ def fields
16
+ @fields ||= []
17
+ end
18
+
19
+ def add_fields(fields)
20
+ self.fields.concat fields
21
+ end
22
+
23
+ def remove(model)
24
+ connection.delete(::Sunspot::Adapters.adapt_instance(model).index_id)
25
+ end
26
+
27
+ protected
28
+ attr_reader :connection
29
+
30
+ def static_hash_for(model)
31
+ { :id => ::Sunspot::Adapters.adapt_instance(model).index_id,
32
+ :type => Indexer.superclasses_for(model.class).map { |clazz| clazz.name }}
33
+ end
34
+ end
35
+
36
+ class <<Indexer
37
+ def add(connection, model)
38
+ self.for(model.class, connection).add(model)
39
+ end
40
+
41
+ def remove(connection, model)
42
+ self.for(model.class, connection).remove(model)
43
+ end
44
+
45
+ def remove_all(connection, clazz = nil)
46
+ connection.delete_by_query("type:#{clazz ? clazz.name : '[* TO *]'}")
47
+ end
48
+
49
+ def for(clazz, connection)
50
+ indexer = self.new(connection)
51
+ for superclass in superclasses_for(clazz)
52
+ indexer.add_fields ::Sunspot::Field.for(superclass)
53
+ indexer.add_fields ::Sunspot::Field.text_for(superclass)
54
+ end
55
+ raise ArgumentError, "Class #{clazz.name} has not been configured for indexing" if indexer.fields.empty?
56
+ indexer
57
+ end
58
+
59
+ def superclasses_for(clazz)
60
+ superclasses_for = [clazz]
61
+ superclasses_for << (clazz = clazz.superclass) while clazz.superclass != Object
62
+ superclasses_for
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,115 @@
1
+ module Sunspot
2
+ class Query
3
+ attr_accessor :keywords, :conditions, :rows, :start, :sort
4
+
5
+ def initialize(types, params, configuration)
6
+ @keywords, @types, @configuration = params[:keywords], types, configuration
7
+ @conditions = Sunspot::Conditions.new(self, params[:conditions] || {})
8
+ paginate(params[:page], params[:per_page])
9
+ self.order = params[:order] if params[:order]
10
+ attributes[:keywords] = @keywords
11
+ params[:conditions].each_pair do |field_name, value|
12
+ attributes[:conditions][field_name.to_sym] = value
13
+ end if params[:conditions]
14
+ end
15
+
16
+ def to_solr
17
+ query_components = []
18
+ query_components << keywords if keywords
19
+ query_components << types_query if types_query
20
+ query_components.map { |component| "(#{component})"} * ' AND '
21
+ end
22
+
23
+ def filter_queries
24
+ scope_queries + condition_queries
25
+ end
26
+
27
+ def add_scope(condition)
28
+ scope << condition
29
+ end
30
+
31
+ def build_condition(field_name, condition_clazz, value)
32
+ condition_clazz.new(field(field_name), value)
33
+ end
34
+
35
+ def paginate(page = nil, per_page = nil)
36
+ page ||= 1
37
+ per_page ||= configuration.pagination.default_per_page
38
+ @start = (page - 1) * per_page
39
+ @rows = per_page
40
+ attributes[:page], attributes[:per_page] = page, per_page
41
+ end
42
+
43
+ def order=(order)
44
+ order_by(*order.split(' '))
45
+ end
46
+
47
+ def order_by(field_name, direction = nil)
48
+ direction ||= :asc
49
+ @sort = [{ field(field_name).indexed_name.to_sym => (direction.to_s == 'asc' ? :ascending : :descending) }] #TODO should support multiple order columns
50
+ attributes[:order] = "#{field_name} #{direction}"
51
+ end
52
+
53
+ def page
54
+ return nil unless start && rows
55
+ start / rows + 1
56
+ end
57
+
58
+ def attributes
59
+ @attributes ||= {
60
+ :order => nil,
61
+ :conditions => fields_hash.keys.inject({}) { |conditions, key| conditions[key.to_sym] = nil; conditions }
62
+ }
63
+ end
64
+
65
+ alias_method :per_page, :rows
66
+
67
+ protected
68
+ attr_accessor :types, :configuration
69
+
70
+ private
71
+
72
+ def scope
73
+ @scope ||= []
74
+ end
75
+
76
+ def scope_queries
77
+ scope.map { |condition| condition.to_solr_query }
78
+ end
79
+
80
+ def condition_queries
81
+ conditions.restrictions.map { |condition| condition.to_solr_query }
82
+ end
83
+
84
+ def types_query
85
+ if types.nil? || types.empty? then "type:[* TO *]"
86
+ elsif types.length == 1 then "type:#{types.first}"
87
+ else "type:(#{types * ' OR '})"
88
+ end
89
+ end
90
+
91
+ def field(field_name)
92
+ fields_hash[field_name.to_s] || raise(ArgumentError, "No field configured for #{types * ', '} with name '#{field_name}'")
93
+ end
94
+
95
+ def fields_hash
96
+ @fields_hash ||= begin
97
+ fields_hash = types.inject({}) do |hash, type|
98
+ ::Sunspot::Field.for(type).each do |field|
99
+ (hash[field.name.to_s] ||= {})[type.name] = field
100
+ end
101
+ hash
102
+ end
103
+ fields_hash.each_pair do |field_name, field_configurations_hash|
104
+ if types.any? { |type| field_configurations_hash[type.name].nil? } # at least one type doesn't have this field configured
105
+ fields_hash.delete(field_name)
106
+ elsif field_configurations_hash.values.map { |configuration| configuration.indexed_name }.uniq.length != 1 # fields with this name have different configs
107
+ fields_hash.delete(field_name)
108
+ else
109
+ fields_hash[field_name] = field_configurations_hash.values.first
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,30 @@
1
+ module Sunspot
2
+ class QueryBuilder
3
+ def initialize(query)
4
+ @query = query
5
+ end
6
+
7
+ def keywords(keywords)
8
+ @query.keywords = keywords
9
+ end
10
+
11
+ def with
12
+ @conditions_builder ||= ::Sunspot::ScopeBuilder.new(@query)
13
+ end
14
+
15
+ def conditions
16
+ @query.conditions
17
+ end
18
+
19
+ def paginate(options = {})
20
+ page = options.delete(:page) || raise(ArgumentError, "paginate requires a :page argument")
21
+ per_page = options.delete(:per_page)
22
+ raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
23
+ @query.paginate(page, per_page)
24
+ end
25
+
26
+ def order_by(field_name, direction = nil)
27
+ @query.order_by(field_name, direction)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,72 @@
1
+ module Sunspot
2
+ module Restriction
3
+ class Base
4
+ def initialize(field, value)
5
+ @field, @value = field, value
6
+ end
7
+
8
+ def to_solr_query
9
+ "#{field.indexed_name}:#{to_solr_conditional}"
10
+ end
11
+
12
+ protected
13
+ attr_accessor :field, :value
14
+
15
+ def solr_value(value = self.value)
16
+ escape field.to_indexed(value)
17
+ end
18
+
19
+ def escape(value)
20
+ Solr::Util.query_parser_escape value
21
+ end
22
+ end
23
+
24
+ class EqualTo < Base
25
+ private
26
+
27
+ def to_solr_conditional
28
+ "#{solr_value}"
29
+ end
30
+ end
31
+
32
+ class LessThan < Base
33
+ private
34
+
35
+ def to_solr_conditional
36
+ "[* TO #{solr_value}]"
37
+ end
38
+ end
39
+
40
+ class GreaterThan < Base
41
+ private
42
+
43
+ def to_solr_conditional
44
+ "[#{solr_value} TO *]"
45
+ end
46
+ end
47
+
48
+ class Between < Base
49
+ private
50
+
51
+ def to_solr_conditional
52
+ "[#{solr_value value.first} TO #{solr_value value.last}]"
53
+ end
54
+ end
55
+
56
+ class AnyOf < Base
57
+ private
58
+
59
+ def to_solr_conditional
60
+ "(#{value.map { |v| solr_value v } * ' OR '})"
61
+ end
62
+ end
63
+
64
+ class AllOf < Base
65
+ private
66
+
67
+ def to_solr_conditional
68
+ "(#{value.map { |v| solr_value v } * ' AND '})"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ module Sunspot
2
+ class ScopeBuilder
3
+ def initialize(query)
4
+ @query = query
5
+ end
6
+
7
+ def method_missing(field_name, *args)
8
+ if args.length == 0 then RestrictionBuilder.new(field_name, @query)
9
+ elsif args.length == 1 then @query.add_scope @query.build_condition(field_name, ::Sunspot::Restriction::EqualTo, args.first)
10
+ else super(field_name.to_sym, *args)
11
+ end
12
+ end
13
+
14
+ class RestrictionBuilder
15
+ def initialize(field_name, query)
16
+ @field_name, @query = field_name, query
17
+ end
18
+
19
+ def method_missing(condition_name, *args)
20
+ clazz = begin
21
+ ::Sunspot::Restriction.const_get(condition_name.to_s.camel_case)
22
+ rescue(NameError)
23
+ super(condition_name.to_sym, *args)
24
+ end
25
+ if value = args.first
26
+ @query.add_scope @query.build_condition(@field_name, clazz, args.first)
27
+ else
28
+ @query.interpret_condition @field_name, clazz
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,83 @@
1
+ module Sunspot
2
+ class Search
3
+ def initialize(connection, configuration, *types, &block)
4
+ @connection = connection
5
+ params = types.last.is_a?(Hash) ? types.pop : {}
6
+ @query = Sunspot::Query.new(types, params, configuration)
7
+ QueryBuilder.new(@query).instance_eval(&block) if block
8
+ @types = types
9
+ end
10
+
11
+ def execute!
12
+ query_options = {}
13
+ query_options[:filter_queries] = query.filter_queries
14
+ query_options[:rows] = query.rows
15
+ query_options[:start] = query.start if query.start
16
+ query_options[:sort] = query.sort if query.sort
17
+ @solr_result = connection.query(query.to_solr, query_options)
18
+ self
19
+ end
20
+
21
+ def results
22
+ @results ||= if query.page && defined?(WillPaginate::Collection)
23
+ WillPaginate::Collection.create(query.page, query.per_page, @solr_result.total_hits) do |pager|
24
+ pager.replace(result_objects)
25
+ end
26
+ else
27
+ result_objects
28
+ end
29
+ end
30
+
31
+ def total
32
+ @total ||= @solr_result.total_hits
33
+ end
34
+
35
+ def attributes
36
+ @query.attributes
37
+ end
38
+
39
+ def order
40
+ @query.attributes[:order]
41
+ end
42
+
43
+ def page
44
+ @query.attributes[:page]
45
+ end
46
+
47
+ def per_page
48
+ @query.attributes[:per_page]
49
+ end
50
+
51
+ def keywords
52
+ @query.attributes[:keywords]
53
+ end
54
+
55
+ def conditions
56
+ ::Sunspot::Util::ClosedStruct.new(@query.attributes[:conditions])
57
+ end
58
+
59
+ protected
60
+ attr_reader :query, :types, :connection
61
+
62
+ private
63
+
64
+ def result_objects
65
+ hit_ids = @solr_result.hits.map { |hit| hit['id'] }
66
+ hit_ids.inject({}) do |type_id_hash, hit_id|
67
+ match = /([^ ]+) (.+)/.match hit_id
68
+ (type_id_hash[match[1]] ||= []) << match[2]
69
+ type_id_hash
70
+ end.inject([]) do |results, pair|
71
+ type_name, ids = pair
72
+ results.concat ::Sunspot::Adapters.adapt_class(type_with_name(type_name)).load_all(ids)
73
+ end.sort_by do |result|
74
+ hit_ids.index(::Sunspot::Adapters.adapt_instance(result).index_id)
75
+ end
76
+ end
77
+
78
+ def type_with_name(type_name)
79
+ @types_cache ||= {}
80
+ @types_cache[type_name] ||= type_name.split('::').inject(Module) { |namespace, name| namespace.const_get(name) }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,43 @@
1
+ module Sunspot
2
+ class Session
3
+ attr_reader :config
4
+
5
+ def initialize(config = Sunspot::Configuration.build, connection = nil)
6
+ @config = config
7
+ yield(@config) if block_given?
8
+ @connection = connection
9
+ end
10
+
11
+ def search(*types, &block)
12
+ ::Sunspot::Search.new(connection, @config, *types, &block).execute!
13
+ end
14
+
15
+ def index(*objects)
16
+ for object in objects
17
+ ::Sunspot::Indexer.add(connection, object)
18
+ end
19
+ end
20
+
21
+ def remove(*objects)
22
+ for object in objects
23
+ ::Sunspot::Indexer.remove(connection, object)
24
+ end
25
+ end
26
+
27
+ def remove_all(*classes)
28
+ if classes.empty?
29
+ ::Sunspot::Indexer.remove_all(connection)
30
+ else
31
+ classes.each do |clazz|
32
+ ::Sunspot::Indexer.remove_all(connection, clazz)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def connection
40
+ @connection ||= Solr::Connection.new(config.solr.url, :autocommit => :on)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ module Sunspot
2
+ module Type
3
+ TextType = Module.new
4
+ StringType = Module.new
5
+ IntegerType = Module.new
6
+ FloatType = Module.new
7
+ TimeType = Module.new
8
+
9
+ class <<TextType
10
+ def indexed_name(name)
11
+ "#{name}_text"
12
+ end
13
+
14
+ def to_indexed(value)
15
+ value.to_s if value
16
+ end
17
+ end
18
+
19
+ class <<StringType
20
+ def indexed_name(name)
21
+ "#{name}_s"
22
+ end
23
+
24
+ def to_indexed(value)
25
+ value.to_s if value
26
+ end
27
+ end
28
+
29
+ class <<IntegerType
30
+ def indexed_name(name)
31
+ "#{name}_i"
32
+ end
33
+
34
+ def to_indexed(value)
35
+ value.to_i.to_s if value
36
+ end
37
+ end
38
+
39
+ class <<FloatType
40
+ def indexed_name(name)
41
+ "#{name}_f"
42
+ end
43
+
44
+ def to_indexed(value)
45
+ value.to_f.to_s if value
46
+ end
47
+ end
48
+
49
+ class <<TimeType
50
+ def indexed_name(name)
51
+ "#{name}_d"
52
+ end
53
+
54
+ def to_indexed(value)
55
+ if value
56
+ time = value.respond_to?(:to_time) ? value.to_time : Time.parse(value.to_s)
57
+ time.utc.strftime('%FT%TZ')
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,13 @@
1
+ module Sunspot
2
+ module Util
3
+ class ClosedStruct
4
+ def initialize(data)
5
+ (class <<self; self; end).module_eval do
6
+ data.each_pair do |attr_name, value|
7
+ define_method(attr_name.to_s) { value }
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Sunspot
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ self
9
+ end
10
+ end
data/lib/sunspot.rb ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ gem 'solr-ruby'
3
+ gem 'extlib'
4
+ require 'solr'
5
+ require 'extlib'
6
+ require File.join(File.dirname(__FILE__), 'light_config')
7
+
8
+ %w(adapters restriction conditions configuration field field_builder indexer query query_builder scope_builder search session type util).each do |filename|
9
+ require File.join(File.dirname(__FILE__), 'sunspot', filename)
10
+ end
11
+
12
+ module Sunspot
13
+ VERSION='0.0.1'
14
+ end
15
+
16
+ class <<Sunspot
17
+ def setup(clazz, &block)
18
+ ::Sunspot::FieldBuilder.new(clazz).instance_eval(&block) if block
19
+ end
20
+
21
+ def index(*objects)
22
+ session.index(*objects)
23
+ end
24
+
25
+ def search(*types, &block)
26
+ session.search(*types, &block)
27
+ end
28
+
29
+ def remove(*objects)
30
+ session.remove(*objects)
31
+ end
32
+
33
+ def remove_all(*classes)
34
+ session.remove_all(*classes)
35
+ end
36
+
37
+ def config
38
+ session.config
39
+ end
40
+
41
+ def reset!
42
+ @session = nil
43
+ end
44
+
45
+ private
46
+
47
+ def session
48
+ @session ||= ::Sunspot::Session.new
49
+ end
50
+ end