UnderpantsGnome-sunspot 0.9.1.1 → 0.9.8.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,47 @@
1
+ == 0.9.0 2009-07-21
2
+ * Use Dismax parser for keyword search
3
+ * Field and document boosting
4
+ * Specify which fields to search in keyword search
5
+ * Allow indexing of multiple values in text fields
6
+ * Access keyword relevance score in Hit objects
7
+ * Allow stored fields, retrieve stored values from Hit objects
8
+ * Support more values in shorthand restrictions
9
+ * Disjunctions and conjunctions
10
+ * Random ordering
11
+ * Control all options for field facets
12
+ * Time range facets
13
+ * Get referenced objects from facets on foreign keys
14
+ * Facet by class
15
+ * Batch indexing
16
+ * New Date field type
17
+ * Direct access to data accessors
18
+ * Executable to configure production Solr instances
19
+ * Replace solr-ruby with RSolr
20
+ * Remove accidental ActiveSupport dependency
21
+
22
+ == 0.8.9 2009-06-23
23
+ * Fix OrderedHash bug in older versions of ActiveSupport
24
+
25
+ == 0.8.8 2009-06-15
26
+ * Escape type names to support namespaced classes
27
+ * Fix bug with anonymous modules in Ruby 1.9
28
+
29
+ == 0.8.7 2009-06-10
30
+ * Add --pid-dir option for sunspot-solr executable
31
+
32
+ == 0.8.5 2009-06-09
33
+ * Added dependencies for sunspot-solr executable to gem dependencies
34
+ * Search for adapters using class ancestors rather than superclasses
35
+
36
+ == 0.8.3 2009-06-03
37
+ * Index objects passed as a collection in a single HTTP request
38
+
39
+ == 0.8.2 2009-05-27
40
+ * Allow specification of Solr home when using sunspot-solr
41
+
42
+ == 0.8.1 2009-05-26
43
+ * Add Search#execute! to public API
44
+
1
45
  == 0.8.0 2009-05-22
2
46
  * Access query API directly; instantiate search without running it
3
47
  * Dynamic fields
data/TODO CHANGED
@@ -1,4 +1,9 @@
1
- === 0.9 ===
2
- * Query-based faceting (?)
1
+ === 0.9.X ===
2
+ * Deal with empty facet queries
3
+ * Passing an integer into the second argument of dynamic_facet() when multiple facets are requested gives the wrong value
3
4
  === 0.10 ===
5
+ * Highlighting
6
+ * LocalSolr
7
+ * Text field restrictions
8
+ * Prefixes
4
9
  * Intelligently decide whether to instantiate all facet rows at once
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 1
3
- :major: 0
4
2
  :minor: 9
3
+ :patch: 8
4
+ :major: 0
data/lib/sunspot.rb CHANGED
@@ -11,8 +11,7 @@ require File.join(File.dirname(__FILE__), 'light_config')
11
11
 
12
12
  %w(util adapters configuration setup composite_setup field field_factory
13
13
  data_extractor indexer query search facet facet_row instantiated_facet
14
- instantiated_facet_row date_facet date_facet_row query_facet query_facet_row
15
- session type dsl).each do |filename|
14
+ instantiated_facet_row facet_data session type dsl).each do |filename|
16
15
  require File.join(File.dirname(__FILE__), 'sunspot', filename)
17
16
  end
18
17
 
data/lib/sunspot/facet.rb CHANGED
@@ -1,51 +1,16 @@
1
- require 'enumerator'
2
-
3
1
  module Sunspot
4
- #
5
- # The facet class encapsulates the information returned by Solr for a
6
- # field facet request.
7
- #
8
- # See http://wiki.apache.org/solr/SolrFacetingOverview for more information
9
- # on Solr's faceting capabilities.
10
- #
11
2
  class Facet
12
- attr_reader :field
13
-
14
- def initialize(facet_values, field) #:nodoc:
15
- @facet_values, @field = facet_values, field
3
+ def initialize(facet_data)
4
+ @facet_data = facet_data
16
5
  end
17
6
 
18
- # The name of the field that contains this facet's values
19
- #
20
- # ==== Returns
21
- #
22
- # Symbol:: The field name
23
- #
24
- def field_name
25
- @field.name
7
+ def name
8
+ @facet_data.name
26
9
  end
10
+ alias_method :field_name, :name
27
11
 
28
- # The rows returned for this facet.
29
- #
30
- # ==== Returns
31
- #
32
- # Array:: Collection of FacetRow objects, in the order returned by Solr
33
- #
34
12
  def rows
35
- @rows ||=
36
- begin
37
- rows = []
38
- @facet_values.each_slice(2) do |pair|
39
- rows << new_row(pair)
40
- end
41
- rows
42
- end
43
- end
44
-
45
- private
46
-
47
- def new_row(pair)
48
- FacetRow.new(pair, self)
13
+ @facet_data.rows { |value, count| FacetRow.new(value, count) }
49
14
  end
50
15
  end
51
16
  end
@@ -0,0 +1,120 @@
1
+ require 'enumerator'
2
+
3
+ module Sunspot
4
+ module FacetData
5
+ class Abstract
6
+ attr_reader :field #:nodoc:
7
+
8
+ def reference
9
+ @field.reference if @field
10
+ end
11
+
12
+ def cast(value)
13
+ if @field
14
+ @field.cast(value)
15
+ else
16
+ value
17
+ end
18
+ end
19
+
20
+ def row_value(value)
21
+ cast(value)
22
+ end
23
+ end
24
+
25
+ class FieldFacetData < Abstract
26
+ def initialize(facet_values, field) #:nodoc:
27
+ @facet_values, @field = facet_values, field
28
+ end
29
+
30
+ # The name of the field that contains this facet's values
31
+ #
32
+ # ==== Returns
33
+ #
34
+ # Symbol:: The field name
35
+ #
36
+ def name
37
+ @field.name
38
+ end
39
+
40
+ # The rows returned for this facet.
41
+ #
42
+ # ==== Returns
43
+ #
44
+ # Array:: Collection of FacetRow objects, in the order returned by Solr
45
+ #
46
+ def rows
47
+ @rows ||=
48
+ begin
49
+ rows = []
50
+ @facet_values.each_slice(2) do |value, count|
51
+ rows << yield(row_value(value), count)
52
+ end
53
+ rows
54
+ end
55
+ end
56
+ end
57
+
58
+ class DateFacetData < FieldFacetData
59
+ def initialize(facet_values, field) #:nodoc:
60
+ @gap = facet_values.delete('gap')[/\+(\d+)SECONDS/,1].to_i
61
+ %w(start end).each { |key| facet_values.delete(key) }
62
+ super(facet_values.to_a.flatten, field)
63
+ end
64
+
65
+ #
66
+ # Get the rows of this date facet, which are instances of DateFacetRow.
67
+ # The rows will always be sorted in chronological order.
68
+ #
69
+ #--
70
+ #
71
+ # The date facet info comes back from Solr as a hash, so we need to sort
72
+ # it manually. FIXME this currently assumes we want to do a "lexical"
73
+ # sort, but we should support count sort as well, even if it's not a
74
+ # common use case.
75
+ #
76
+ def rows(&block)
77
+ super(&block).sort { |a, b| a.value.first <=> b.value.first }
78
+ end
79
+
80
+ private
81
+
82
+ def row_value(value)
83
+ cast(value)..(cast(value) + @gap)
84
+ end
85
+ end
86
+
87
+ class QueryFacetData < Abstract
88
+ def initialize(outgoing_query_facet, row_data) #:nodoc:
89
+ @outgoing_query_facet, @row_data = outgoing_query_facet, row_data
90
+ @field = @outgoing_query_facet.field
91
+ end
92
+
93
+ def name
94
+ outgoing_query_facet.name
95
+ end
96
+
97
+ #
98
+ # Get the rows associated with this query facet. Returned rows are always
99
+ # ordered by count.
100
+ #
101
+ # ==== Returns
102
+ #
103
+ # Array:: Collection of QueryFacetRow objects, ordered by count
104
+ #
105
+ def rows
106
+ @rows ||=
107
+ begin
108
+ rows = []
109
+ for row in @outgoing_query_facet.rows
110
+ row_query = row.to_boolean_phrase
111
+ if @row_data.has_key?(row_query)
112
+ rows << yield(row.label, @row_data[row_query])
113
+ end
114
+ end
115
+ rows.sort! { |x, y| y.count <=> x.count }
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -1,34 +1,10 @@
1
1
  module Sunspot
2
2
  # This class encapsulates a facet row (value) for a facet.
3
3
  class FacetRow
4
- def initialize(pair, facet) #:nodoc:
5
- @pair, @facet = pair, facet
6
- end
7
-
8
- # The value associated with the facet. This will be cast according to the
9
- # field's type; so, for an integer field, this method will return an
10
- # integer, etc.
11
- #
12
- # Note that <strong>+Time+ fields will always return facet values in
13
- # UTC</strong>.
14
- #
15
- # ==== Returns
16
- #
17
- # Object:: The value associated with the row, cast to the appropriate type
18
- #
19
- def value
20
- @value ||= @facet.field.cast(@pair[0])
21
- end
4
+ attr_reader :value, :count
22
5
 
23
- # The number of documents matching the search parameters that have this
24
- # value in the facet's field.
25
- #
26
- # ==== Returns
27
- #
28
- # Integer:: Document count for this value
29
- #
30
- def count
31
- @count ||= @pair[1]
6
+ def initialize(value, count) #:nodoc:
7
+ @value, @count = value, count
32
8
  end
33
9
  end
34
10
  end
@@ -15,7 +15,7 @@ module Sunspot
15
15
  #
16
16
  def populate_instances! #:nodoc:
17
17
  ids = rows.map { |row| row.value }
18
- reference_class = Sunspot::Util.full_const_get(@field.reference.to_s)
18
+ reference_class = Sunspot::Util.full_const_get(@facet_data.reference.to_s)
19
19
  accessor = Adapters::DataAccessor.create(reference_class)
20
20
  instance_map = accessor.load_all(ids).inject({}) do |map, instance|
21
21
  map[Adapters::InstanceAdapter.adapt(instance).id] = instance
@@ -26,6 +26,10 @@ module Sunspot
26
26
  end
27
27
  end
28
28
 
29
+ def rows
30
+ @facet_data.rows { |value, count| InstantiatedFacetRow.new(value, count, self) }
31
+ end
32
+
29
33
  private
30
34
 
31
35
  #
@@ -2,6 +2,16 @@ module Sunspot
2
2
  class InstantiatedFacetRow < FacetRow
3
3
  attr_writer :instance
4
4
 
5
+ def initialize(value, count, facet)
6
+ super(value, count)
7
+ @facet = facet
8
+ end
9
+
10
+ #
11
+ # Get the persistent object referenced by this row's value. Instances are
12
+ # batch-lazy-loaded, which means that for a given facet, all of the
13
+ # instances are loaded the first time any row's instance is requested.
14
+ #
5
15
  def instance
6
16
  unless defined?(@instance)
7
17
  @facet.populate_instances!
data/lib/sunspot/query.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  %w(base_query scope field_query connective dynamic_query field_facet query_facet
2
- query_facet_row pagination restriction sort sort_composite).each do |file|
2
+ query_facet_row query_field_facet pagination restriction sort
3
+ sort_composite).each do |file|
3
4
  require File.join(File.dirname(__FILE__), 'query', file)
4
5
  end
5
6
 
@@ -20,12 +21,12 @@ module Sunspot
20
21
  class Query < FieldQuery
21
22
  attr_reader :query_facets #:nodoc:
22
23
 
23
- def initialize(setup, configuration) #:nodoc:
24
- @setup, @configuration = setup, configuration
24
+ def initialize(types, setup, configuration) #:nodoc:
25
+ @setup = setup
25
26
  @components = []
26
27
  @query_facets = {}
27
- @components << @base_query = BaseQuery.new(setup)
28
- @components << @pagination = Pagination.new(@configuration)
28
+ @components << @base_query = BaseQuery.new(types, setup)
29
+ @components << @pagination = Pagination.new(configuration)
29
30
  @components << @sort = SortComposite.new
30
31
  end
31
32
 
@@ -9,8 +9,8 @@ module Sunspot
9
9
 
10
10
  attr_writer :keywords
11
11
 
12
- def initialize(setup)
13
- @setup = setup
12
+ def initialize(types, setup)
13
+ @types, @setup = types, setup
14
14
  end
15
15
 
16
16
  #
@@ -64,7 +64,7 @@ module Sunspot
64
64
  #
65
65
  def escaped_types
66
66
  @escaped_types ||=
67
- @setup.type_names.map { |name| escape(name)}
67
+ @types.map { |type| escape(type.name)}
68
68
  end
69
69
 
70
70
  #
@@ -5,8 +5,8 @@ module Sunspot
5
5
  # Base class for connectives (conjunctions and disjunctions).
6
6
  #
7
7
  class Abstract < Scope
8
- def initialize(setup) #:nodoc:
9
- @setup = setup
8
+ def initialize(setup, negated = false) #:nodoc:
9
+ @setup, @negated = setup, negated
10
10
  @components = []
11
11
  end
12
12
 
@@ -21,7 +21,7 @@ module Sunspot
21
21
  # Express the connective as a Lucene boolean phrase.
22
22
  #
23
23
  def to_boolean_phrase #:nodoc:
24
- if @components.length == 1
24
+ phrase = if @components.length == 1
25
25
  @components.first.to_boolean_phrase
26
26
  else
27
27
  component_phrases = @components.map do |component|
@@ -29,6 +29,11 @@ module Sunspot
29
29
  end
30
30
  "(#{component_phrases.join(" #{connector} ")})"
31
31
  end
32
+ if negated?
33
+ "-#{phrase}"
34
+ else
35
+ phrase
36
+ end
32
37
  end
33
38
 
34
39
  #
@@ -38,12 +43,38 @@ module Sunspot
38
43
  def add_component(component) #:nodoc:
39
44
  @components << component
40
45
  end
46
+
47
+ def negated?
48
+ @negated
49
+ end
50
+
51
+ def negate
52
+ negated = self.class.new(@setup, !negated?)
53
+ for component in @components
54
+ negated.add_component(component)
55
+ end
56
+ negated
57
+ end
41
58
  end
42
59
 
43
60
  #
44
61
  # Disjunctions combine their components with an OR operator.
45
62
  #
46
63
  class Disjunction < Abstract
64
+ class <<self
65
+ def inverse
66
+ Conjunction
67
+ end
68
+ end
69
+
70
+ def to_boolean_phrase
71
+ if @components.any? { |component| component.negated? }
72
+ denormalize.to_boolean_phrase
73
+ else
74
+ super
75
+ end
76
+ end
77
+
47
78
  #
48
79
  # Add a conjunction to the disjunction. This overrides the method in
49
80
  # the Scope class since scopes are implicitly conjunctive and thus
@@ -55,17 +86,35 @@ module Sunspot
55
86
  conjunction
56
87
  end
57
88
 
89
+ def add_disjunction
90
+ self
91
+ end
92
+
58
93
  private
59
94
 
60
95
  def connector
61
96
  'OR'
62
97
  end
98
+
99
+ def denormalize
100
+ denormalized = self.class.inverse.new(@setup, !negated?)
101
+ for component in @components
102
+ denormalized.add_component(component.negate)
103
+ end
104
+ denormalized
105
+ end
63
106
  end
64
107
 
65
108
  #
66
109
  # Conjunctions combine their components with an AND operator.
67
110
  #
68
111
  class Conjunction < Abstract
112
+ class <<self
113
+ def inverse
114
+ Disjunction
115
+ end
116
+ end
117
+
69
118
  private
70
119
 
71
120
  def connector