UnderpantsGnome-sunspot 0.9.1.1

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.
Files changed (103) hide show
  1. data/History.txt +39 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +4 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +470 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/date_facet.rb +36 -0
  16. data/lib/sunspot/date_facet_row.rb +17 -0
  17. data/lib/sunspot/dsl.rb +3 -0
  18. data/lib/sunspot/dsl/field_query.rb +72 -0
  19. data/lib/sunspot/dsl/fields.rb +86 -0
  20. data/lib/sunspot/dsl/query.rb +59 -0
  21. data/lib/sunspot/dsl/query_facet.rb +31 -0
  22. data/lib/sunspot/dsl/restriction.rb +25 -0
  23. data/lib/sunspot/dsl/scope.rb +193 -0
  24. data/lib/sunspot/dsl/search.rb +30 -0
  25. data/lib/sunspot/facet.rb +51 -0
  26. data/lib/sunspot/facet_row.rb +34 -0
  27. data/lib/sunspot/field.rb +157 -0
  28. data/lib/sunspot/field_factory.rb +126 -0
  29. data/lib/sunspot/indexer.rb +127 -0
  30. data/lib/sunspot/instantiated_facet.rb +38 -0
  31. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  32. data/lib/sunspot/query.rb +190 -0
  33. data/lib/sunspot/query/base_query.rb +90 -0
  34. data/lib/sunspot/query/connective.rb +77 -0
  35. data/lib/sunspot/query/dynamic_query.rb +69 -0
  36. data/lib/sunspot/query/field_facet.rb +149 -0
  37. data/lib/sunspot/query/field_query.rb +57 -0
  38. data/lib/sunspot/query/pagination.rb +39 -0
  39. data/lib/sunspot/query/query_facet.rb +72 -0
  40. data/lib/sunspot/query/query_facet_row.rb +19 -0
  41. data/lib/sunspot/query/restriction.rb +225 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/query_facet.rb +33 -0
  46. data/lib/sunspot/query_facet_row.rb +21 -0
  47. data/lib/sunspot/schema.rb +165 -0
  48. data/lib/sunspot/search.rb +222 -0
  49. data/lib/sunspot/search/hit.rb +62 -0
  50. data/lib/sunspot/session.rb +201 -0
  51. data/lib/sunspot/setup.rb +271 -0
  52. data/lib/sunspot/type.rb +200 -0
  53. data/lib/sunspot/util.rb +164 -0
  54. data/solr/etc/jetty.xml +212 -0
  55. data/solr/etc/webdefault.xml +379 -0
  56. data/solr/lib/jetty-6.1.3.jar +0 -0
  57. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  58. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  59. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  60. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  61. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  62. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  63. data/solr/solr/conf/elevate.xml +36 -0
  64. data/solr/solr/conf/protwords.txt +21 -0
  65. data/solr/solr/conf/schema.xml +50 -0
  66. data/solr/solr/conf/solrconfig.xml +696 -0
  67. data/solr/solr/conf/stopwords.txt +57 -0
  68. data/solr/solr/conf/synonyms.txt +31 -0
  69. data/solr/start.jar +0 -0
  70. data/solr/webapps/solr.war +0 -0
  71. data/spec/api/adapters_spec.rb +33 -0
  72. data/spec/api/build_search_spec.rb +918 -0
  73. data/spec/api/indexer_spec.rb +311 -0
  74. data/spec/api/query_spec.rb +153 -0
  75. data/spec/api/search_retrieval_spec.rb +325 -0
  76. data/spec/api/session_spec.rb +157 -0
  77. data/spec/api/spec_helper.rb +1 -0
  78. data/spec/api/sunspot_spec.rb +18 -0
  79. data/spec/integration/dynamic_fields_spec.rb +55 -0
  80. data/spec/integration/faceting_spec.rb +169 -0
  81. data/spec/integration/keyword_search_spec.rb +83 -0
  82. data/spec/integration/scoped_search_spec.rb +188 -0
  83. data/spec/integration/spec_helper.rb +1 -0
  84. data/spec/integration/stored_fields_spec.rb +10 -0
  85. data/spec/integration/test_pagination.rb +32 -0
  86. data/spec/mocks/adapters.rb +32 -0
  87. data/spec/mocks/blog.rb +3 -0
  88. data/spec/mocks/comment.rb +19 -0
  89. data/spec/mocks/connection.rb +84 -0
  90. data/spec/mocks/mock_adapter.rb +30 -0
  91. data/spec/mocks/mock_record.rb +41 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +70 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +25 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +21 -0
  99. data/tasks/schema.rake +19 -0
  100. data/tasks/spec.rake +24 -0
  101. data/tasks/todo.rake +4 -0
  102. data/templates/schema.xml.haml +24 -0
  103. metadata +245 -0
@@ -0,0 +1,165 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The Scope class encapsulates a set of restrictions that scope search
5
+ # results (as well as query facets rows). This class's API is exposed by
6
+ # Query::Query and Query::QueryFacetRow.
7
+ #
8
+ class Scope
9
+ #
10
+ # Add a restriction to the query.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: Name of the field to which the restriction applies
15
+ # restriction_type<Class,Symbol>::
16
+ # Subclass of Sunspot::Query::Restriction::Base, or snake_cased name as symbol
17
+ # (e.g., +:equal_to+)
18
+ # value<Object>::
19
+ # Value against which the restriction applies (e.g. less_than(2) has a
20
+ # value of 2)
21
+ # negated::
22
+ # Whether this restriction should be negated (use add_negated_restriction)
23
+ #
24
+ def add_restriction(field_name, restriction_type, value, negated = false)
25
+ if restriction_type.is_a?(Symbol)
26
+ restriction_type = Restriction[restriction_type]
27
+ end
28
+ add_component(
29
+ restriction = restriction_type.new(
30
+ build_field(field_name), value, negated
31
+ )
32
+ )
33
+ restriction
34
+ end
35
+
36
+ #
37
+ # Add a negated restriction to the query. The restriction will be taken as
38
+ # the opposite of its usual meaning (e.g., an :equal_to restriction will
39
+ # be "not equal to".
40
+ #
41
+ # ==== Parameters
42
+ #
43
+ # field_name<Symbol>:: Name of the field to which the restriction applies
44
+ # restriction_type<Class>::
45
+ # Subclass of Sunspot::Query::Restriction::Base to instantiate
46
+ # value<Object>::
47
+ # Value against which the restriction applies (e.g. less_than(2) has a
48
+ # value of 2)
49
+ #
50
+ def add_negated_restriction(field_name, restriction_type, value)
51
+ add_restriction(field_name, restriction_type, value, true)
52
+ end
53
+
54
+ #
55
+ # Add a disjunction to the scope. The disjunction can then take a set of
56
+ # restrictions, which are combined with OR semantics.
57
+ #
58
+ # ==== Returns
59
+ #
60
+ # Connective::Disjunction:: New disjunction
61
+ #
62
+ def add_disjunction
63
+ add_component(disjunction = Connective::Disjunction.new(setup))
64
+ disjunction
65
+ end
66
+
67
+ #
68
+ # Add a conjunction to the scope. In most cases, this will simply return
69
+ # the Scope object itself, since scopes by default combine their
70
+ # restrictions with OR semantics. The Connective::Disjunction class
71
+ # overrides this method to return a Connective::Conjunction.
72
+ #
73
+ # ==== Returns
74
+ #
75
+ # Scope:: Self or another scope with conjunctive semantics.
76
+ #
77
+ def add_conjunction
78
+ self
79
+ end
80
+
81
+ #
82
+ # Exclude a particular instance from the search results
83
+ #
84
+ # ==== Parameters
85
+ #
86
+ # instance<Object>:: instance to exclude from results
87
+ #
88
+ def exclude_instance(instance)
89
+ add_component(Restriction::SameAs.new(instance, true))
90
+ end
91
+
92
+ #
93
+ # Generate a DynamicQuery instance for the given base name.
94
+ # This gives you access to a subset of the Query API but the operations
95
+ # apply to dynamic fields inside the dynamic field definition specified
96
+ # by +base_name+.
97
+ #
98
+ # ==== Parameters
99
+ #
100
+ # base_name<Symbol>::
101
+ # Base name of the dynamic field definition to use in the dynamic query
102
+ # operations
103
+ #
104
+ # ==== Returns
105
+ #
106
+ # DynamicQuery::
107
+ # Instance providing dynamic query functionality for the given field
108
+ # definitions.
109
+ #
110
+ def dynamic_query(base_name)
111
+ DynamicQuery.new(setup.dynamic_field_factory(base_name), self)
112
+ end
113
+
114
+ #
115
+ # Determine which restriction type to add based on the type of the value.
116
+ # Used to interpret query conditions passed as a hash, as well as the
117
+ # short-form DSL::Scope#with method.
118
+ #
119
+ # ==== Parameters
120
+ #
121
+ # field_name<Symbol>:: Name of the field on which to apply the restriction
122
+ # value<Object,Array,Range>:: Value to which to apply to the restriction
123
+ #--
124
+ # negated<Boolean>:: Whether to negate the restriction.
125
+ #
126
+ def add_shorthand_restriction(field_name, value, negated = false) #:nodoc:
127
+ restriction_type =
128
+ case value
129
+ when Range
130
+ Restriction::Between
131
+ when Array
132
+ Restriction::AnyOf
133
+ else
134
+ Restriction::EqualTo
135
+ end
136
+ add_restriction(field_name, restriction_type, value, negated)
137
+ end
138
+
139
+ #
140
+ # Add a negated shorthand restriction. See #add_shorthand_restriction
141
+ #
142
+ def add_negated_shorthand_restriction(field_name, value)
143
+ add_shorthand_restriction(field_name, value, true)
144
+ end
145
+
146
+ private
147
+
148
+ #
149
+ # Build a field with the given field name. Subclasses may override this
150
+ # method.
151
+ #
152
+ def build_field(field_name)
153
+ setup.field(field_name)
154
+ end
155
+
156
+ #
157
+ # Return a setup object which can return a field object given a name.
158
+ # Subclasses may override this method.
159
+ #
160
+ def setup
161
+ @setup
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,36 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The Sort class is a query component representing a sort by a given field.
5
+ #
6
+ class Sort #:nodoc:
7
+ DIRECTIONS = {
8
+ :asc => 'asc',
9
+ :ascending => 'asc',
10
+ :desc => 'desc',
11
+ :descending => 'desc'
12
+ }
13
+
14
+ def initialize(field, direction = nil)
15
+ if field.multiple?
16
+ raise(ArgumentError, "#{field.name} cannot be used for ordering because it is a multiple-value field")
17
+ end
18
+ @field, @direction = field, (direction || :asc).to_sym
19
+ end
20
+
21
+ def to_param
22
+ "#{@field.indexed_name.to_sym} #{direction_for_solr}"
23
+ end
24
+
25
+ private
26
+
27
+ def direction_for_solr
28
+ DIRECTIONS[@direction] ||
29
+ raise(
30
+ ArgumentError,
31
+ "Unknown sort direction #{@direction}. Acceptable input is: #{DIRECTIONS.keys.map { |input| input.inspect } * ', '}"
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The SortComposite class encapsulates an ordered collection of Sort
5
+ # objects. It's necessary to keep this as a separate class as Solr takes
6
+ # the sort as a single parameter, so adding sorts as regular components
7
+ # would not merge correctly in the #to_params method.
8
+ #
9
+ class SortComposite #:nodoc:
10
+ def initialize
11
+ @sorts = []
12
+ end
13
+
14
+ #
15
+ # Add a sort to the composite
16
+ #
17
+ def <<(sort)
18
+ @sorts << sort
19
+ end
20
+
21
+ #
22
+ # Combine the sorts into a single param by joining them
23
+ #
24
+ def to_params
25
+ unless @sorts.empty?
26
+ { :sort => @sorts.map { |sort| sort.to_param } * ', ' }
27
+ else
28
+ {}
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Sunspot
2
+ #
3
+ # QueryFacet instances encapsulate a set of query facet results. Each facet
4
+ # corresponds to a group of rows defined inside a DSL::FieldQuery#facet block.
5
+ #
6
+ class QueryFacet
7
+ def initialize(outgoing_query_facet, row_data) #:nodoc:
8
+ @outgoing_query_facet, @row_data = outgoing_query_facet, row_data
9
+ end
10
+
11
+ #
12
+ # Get the rows associated with this query facet. Returned rows are always
13
+ # ordered by count.
14
+ #
15
+ # ==== Returns
16
+ #
17
+ # Array:: Collection of QueryFacetRow objects, ordered by count
18
+ #
19
+ def rows
20
+ @rows ||=
21
+ begin
22
+ rows = []
23
+ for row in @outgoing_query_facet.rows
24
+ row_query = row.to_boolean_phrase
25
+ if @row_data.has_key?(row_query)
26
+ rows << QueryFacetRow.new(row.label, @row_data[row_query])
27
+ end
28
+ end
29
+ rows.sort! { |x, y| y.count <=> x.count }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ module Sunspot
2
+ #
3
+ # Objects of this class encapsulate a single query facet row returned for a
4
+ # query facet.
5
+ #
6
+ class QueryFacetRow
7
+ #
8
+ # This is the "label" passed into the query facet row when it is defined in
9
+ # the search.
10
+ #
11
+ attr_reader :value
12
+ #
13
+ # Number of documents in the result set that match this facet's scope.
14
+ #
15
+ attr_reader :count
16
+
17
+ def initialize(value, count) #:nodoc:
18
+ @value, @count = value, count
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,165 @@
1
+ using_rubygems = false
2
+ begin
3
+ require 'haml'
4
+ rescue LoadError => e
5
+ if using_rubygems
6
+ raise(e)
7
+ else
8
+ using_rubygems = true
9
+ require 'rubygems'
10
+ retry
11
+ end
12
+ end
13
+
14
+ module Sunspot
15
+ #
16
+ # Object that encapsulates schema information for building a Solr schema.xml
17
+ # file. This class is used by the schema:compile task as well as the
18
+ # sunspot-configure-solr executable.
19
+ #
20
+ class Schema #:nodoc:all
21
+ FieldType = Struct.new(:name, :class_name, :suffix)
22
+ FieldVariant = Struct.new(:attribute, :suffix)
23
+
24
+ DEFAULT_TOKENIZER = 'solr.StandardTokenizerFactory'
25
+ DEFAULT_FILTERS = %w(solr.StandardFilterFactory solr.LowerCaseFilterFactory)
26
+
27
+ FIELD_TYPES = [
28
+ FieldType.new('boolean', 'Bool', 'b'),
29
+ FieldType.new('sfloat', 'SortableFloat', 'f'),
30
+ FieldType.new('date', 'Date', 'd'),
31
+ FieldType.new('sint', 'SortableInt', 'i'),
32
+ FieldType.new('string', 'Str', 's')
33
+ ]
34
+
35
+ FIELD_VARIANTS = [
36
+ FieldVariant.new('multiValued', 'm'),
37
+ FieldVariant.new('stored', 's')
38
+ ]
39
+
40
+ attr_reader :tokenizer, :filters
41
+
42
+ def initialize
43
+ @tokenizer = DEFAULT_TOKENIZER
44
+ @filters = DEFAULT_FILTERS.dup
45
+ end
46
+
47
+ #
48
+ # Attribute field types defined in the schema
49
+ #
50
+ def types
51
+ FIELD_TYPES
52
+ end
53
+
54
+ #
55
+ # DynamicField instances representing all the available types and variants
56
+ #
57
+ def dynamic_fields
58
+ fields = []
59
+ for field_variants in variant_combinations
60
+ for type in FIELD_TYPES
61
+ fields << DynamicField.new(type, field_variants)
62
+ end
63
+ end
64
+ fields
65
+ end
66
+
67
+ #
68
+ # Which tokenizer to use for text fields
69
+ #
70
+ def tokenizer=(tokenizer)
71
+ @tokenizer =
72
+ if tokenizer =~ /\./
73
+ tokenizer
74
+ else
75
+ "solr.#{tokenizer}TokenizerFactory"
76
+ end
77
+ end
78
+
79
+ #
80
+ # Add a filter for text field tokenization
81
+ #
82
+ def add_filter(filter)
83
+ @filters <<
84
+ if filter =~ /\./
85
+ filter
86
+ else
87
+ "solr.#{filter}FilterFactory"
88
+ end
89
+ end
90
+
91
+ #
92
+ # Return an XML representation of this schema using the Haml template
93
+ #
94
+ def to_xml
95
+ template = File.read(
96
+ File.join(
97
+ File.dirname(__FILE__),
98
+ '..',
99
+ '..',
100
+ 'templates',
101
+ 'schema.xml.haml'
102
+ )
103
+ )
104
+ engine = Haml::Engine.new(template)
105
+ engine.render(Object.new, :schema => self)
106
+ end
107
+
108
+ private
109
+
110
+ #
111
+ # All of the possible combinations of variants
112
+ #
113
+ def variant_combinations
114
+ combinations = []
115
+ 0.upto(2 ** FIELD_VARIANTS.length - 1) do |b|
116
+ combinations << combination = []
117
+ FIELD_VARIANTS.each_with_index do |variant, i|
118
+ combination << variant if b & 1<<i > 0
119
+ end
120
+ end
121
+ combinations
122
+ end
123
+
124
+ #
125
+ # Represents a dynamic field (in the Solr schema sense, not the Sunspot
126
+ # sense).
127
+ #
128
+ class DynamicField
129
+ def initialize(type, field_variants)
130
+ @type, @field_variants = type, field_variants
131
+ end
132
+
133
+ #
134
+ # Name of the field in the schema
135
+ #
136
+ def name
137
+ variant_suffixes = @field_variants.map { |variant| variant.suffix }.join
138
+ "*_#{@type.suffix}#{variant_suffixes}"
139
+ end
140
+
141
+ #
142
+ # Name of the type as defined in the schema
143
+ #
144
+ def type
145
+ @type.name
146
+ end
147
+
148
+ #
149
+ # Implement magic methods to ask if a field is of a particular variant.
150
+ # Returns "true" if the field is of that variant and "false" otherwise.
151
+ #
152
+ def method_missing(name, *args, &block)
153
+ if name.to_s =~ /\?$/ && args.empty?
154
+ if @field_variants.any? { |variant| "#{variant.attribute}?" == name.to_s }
155
+ 'true'
156
+ else
157
+ 'false'
158
+ end
159
+ else
160
+ super(name.to_sym, *args, &block)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end