sunspot 2.2.8 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/lib/sunspot/configuration.rb +1 -0
- data/lib/sunspot/dsl/field_query.rb +11 -0
- data/lib/sunspot/dsl/field_stats.rb +7 -0
- data/lib/sunspot/query.rb +3 -3
- data/lib/sunspot/query/abstract_json_field_facet.rb +70 -0
- data/lib/sunspot/query/date_field_json_facet.rb +25 -0
- data/lib/sunspot/query/field_json_facet.rb +19 -0
- data/lib/sunspot/query/field_stats.rb +35 -2
- data/lib/sunspot/query/range_json_facet.rb +28 -0
- data/lib/sunspot/schema.rb +10 -2
- data/lib/sunspot/search.rb +4 -3
- data/lib/sunspot/search/abstract_search.rb +13 -0
- data/lib/sunspot/search/field_json_facet.rb +33 -0
- data/lib/sunspot/search/json_facet_row.rb +40 -0
- data/lib/sunspot/search/json_facet_stats.rb +23 -0
- data/lib/sunspot/search/stats_json_row.rb +82 -0
- data/lib/sunspot/search/stats_row.rb +3 -1
- data/lib/sunspot/session.rb +7 -5
- data/lib/sunspot/util.rb +23 -0
- data/lib/sunspot/version.rb +1 -1
- data/spec/api/query/fulltext_examples.rb +16 -6
- data/spec/helpers/integration_helper.rb +1 -0
- data/spec/integration/faceting_spec.rb +213 -0
- data/spec/integration/scoped_search_spec.rb +1 -1
- data/spec/integration/stats_spec.rb +44 -3
- data/spec/mocks/post.rb +4 -0
- data/spec/spec_helper.rb +2 -6
- data/sunspot.gemspec +1 -1
- metadata +15 -7
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 698b438f344a3e391c87d9f755dc227869fefad88555b261ef4dcd9cc9d9c1d1
         | 
| 4 | 
            +
              data.tar.gz: 2ba1b782a43a5eafd6f1bc6397ef221fe94bbbb92f1036505530719a50f07882
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 8f89e196fd125f6c0b2bd8eda2aecf7c288860ff14ab128d695f61252344fc07b0011ed8c10d8c5578227eae814df368fecf3332d306ae2a79e9b5da593dfc97
         | 
| 7 | 
            +
              data.tar.gz: 780ca66b8d6e569a994670b2e6f990444333dc03f109bd015c7f26924673e801281f611f5d08b14b48afb35b6177099747113e0bbdc10a07aebe4cee5f93c8ea
         | 
| @@ -333,6 +333,17 @@ module Sunspot | |
| 333 333 | 
             
                    end
         | 
| 334 334 | 
             
                  end
         | 
| 335 335 |  | 
| 336 | 
            +
                  def json_facet(*field_names)
         | 
| 337 | 
            +
                    options = Sunspot::Util.extract_options_from(field_names)
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                    field_names.each do |field_name|
         | 
| 340 | 
            +
                      field = @setup.field(field_name)
         | 
| 341 | 
            +
                      facet = Sunspot::Util.parse_json_facet(field_name, options, @setup)
         | 
| 342 | 
            +
                      @search.add_json_facet(field, options)
         | 
| 343 | 
            +
                      @query.add_query_facet(facet)
         | 
| 344 | 
            +
                    end
         | 
| 345 | 
            +
                  end
         | 
| 346 | 
            +
             | 
| 336 347 | 
             
                  def stats(*field_names, &block)
         | 
| 337 348 | 
             
                    options = Sunspot::Util.extract_options_from(field_names)
         | 
| 338 349 |  | 
| @@ -13,6 +13,13 @@ module Sunspot | |
| 13 13 | 
             
                      @search_stats.add_facet(field)
         | 
| 14 14 | 
             
                    end
         | 
| 15 15 | 
             
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def json_facet(field_name, options = {})
         | 
| 18 | 
            +
                    field = @setup.field(field_name)
         | 
| 19 | 
            +
                    facet = Sunspot::Util.parse_json_facet(field_name, options, @setup)
         | 
| 20 | 
            +
                    @query_stats.add_json_facet(facet)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 16 23 | 
             
                end
         | 
| 17 24 | 
             
              end
         | 
| 18 25 | 
             
            end
         | 
    
        data/lib/sunspot/query.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 | 
            -
            %w(filter abstract_field_facet connective boost_query date_field_facet
         | 
| 2 | 
            -
               range_facet abstract_fulltext dismax join | 
| 3 | 
            -
               field_facet highlighting pagination restriction common_query spellcheck
         | 
| 1 | 
            +
            %w(filter abstract_field_facet abstract_json_field_facet connective boost_query date_field_facet field_json_facet
         | 
| 2 | 
            +
               range_facet range_json_facet date_field_json_facet abstract_fulltext dismax join
         | 
| 3 | 
            +
               field_list field_facet highlighting pagination restriction common_query spellcheck
         | 
| 4 4 | 
             
               standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
         | 
| 5 5 | 
             
               scope sort sort_composite text_field_boost function_query field_stats
         | 
| 6 6 | 
             
               composite_fulltext group group_query).each do |file|
         | 
| @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class AbstractJsonFieldFacet
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attr_accessor :field
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  DISTINCT_STRATEGIES = [:unique, :hll]
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def initialize(field, options, setup)
         | 
| 10 | 
            +
                    @field, @options, @setup = field, options, setup
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def init_params
         | 
| 14 | 
            +
                    params = {}
         | 
| 15 | 
            +
                    params[:limit] = @options[:limit] unless @options[:limit].nil?
         | 
| 16 | 
            +
                    params[:mincount] = @options[:minimum_count] unless @options[:minimum_count].nil?
         | 
| 17 | 
            +
                    params[:sort] = { @options[:sort] => @options[:sort_type]||'desc' } unless @options[:sort].nil?
         | 
| 18 | 
            +
                    params[:prefix] = @options[:prefix] unless @options[:prefix].nil?
         | 
| 19 | 
            +
                    params[:offset] = @options[:offset] unless @options[:offset].nil?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    if !@options[:distinct].nil?
         | 
| 22 | 
            +
                      dist_opts = @options[:distinct]
         | 
| 23 | 
            +
                      raise Exception.new("Need to specify a strategy") if dist_opts[:strategy].nil?
         | 
| 24 | 
            +
                      raise Exception.new("The strategy must be one of #{DISTINCT_STRATEGIES}") unless DISTINCT_STRATEGIES.include?(dist_opts[:strategy])
         | 
| 25 | 
            +
                      @stategy = dist_opts[:strategy]
         | 
| 26 | 
            +
                      @group_by = dist_opts[:group_by].nil? ? @field : @setup.field(dist_opts[:group_by])
         | 
| 27 | 
            +
                      params[:field] = @group_by.indexed_name
         | 
| 28 | 
            +
                      params[:facet] = {}
         | 
| 29 | 
            +
                      params[:facet][:distinct] = "#{@stategy}(#{@field.indexed_name})"
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    params
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def get_params
         | 
| 36 | 
            +
                    query = field_name_with_local_params
         | 
| 37 | 
            +
                    nested_params = recursive_nested_params(@options)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    if !nested_params.nil?
         | 
| 40 | 
            +
                      query[@field.name][:facet] ||= {}
         | 
| 41 | 
            +
                      query[@field.name][:facet].merge!(nested_params)
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                    query
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def to_params
         | 
| 47 | 
            +
                    { 'json.facet' => self.get_params.to_json }
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  private
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def recursive_nested_params(options)
         | 
| 53 | 
            +
                    if !options[:nested].nil? && options[:nested].is_a?(Hash)
         | 
| 54 | 
            +
                      opts = options[:nested]
         | 
| 55 | 
            +
                      field_name = opts[:field]
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                      options = Sunspot::Util.extract_options_from([opts])
         | 
| 58 | 
            +
                      params = Sunspot::Util.parse_json_facet(field_name, options, @setup).field_name_with_local_params
         | 
| 59 | 
            +
                      if !opts.nil?
         | 
| 60 | 
            +
                        nested_params = recursive_nested_params(opts)
         | 
| 61 | 
            +
                        params[field_name][:facet] = nested_params unless nested_params.nil?
         | 
| 62 | 
            +
                      end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                      params
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class DateFieldJsonFacet < AbstractJsonFieldFacet
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def initialize(field, options, setup)
         | 
| 6 | 
            +
                    raise Exception.new('Need to specify a time_range') if options[:time_range].nil?
         | 
| 7 | 
            +
                    @start = options[:time_range].first
         | 
| 8 | 
            +
                    @end = options[:time_range].last
         | 
| 9 | 
            +
                    @gap = "+#{options[:gap] || 86400}SECONDS"
         | 
| 10 | 
            +
                    super
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def field_name_with_local_params
         | 
| 14 | 
            +
                    params = {}
         | 
| 15 | 
            +
                    params[:type] = 'range'
         | 
| 16 | 
            +
                    params[:field] = @field.indexed_name
         | 
| 17 | 
            +
                    params[:start] = @field.to_indexed(@start)
         | 
| 18 | 
            +
                    params[:end] = @field.to_indexed(@end)
         | 
| 19 | 
            +
                    params[:gap] = @gap
         | 
| 20 | 
            +
                    params.merge!(init_params)
         | 
| 21 | 
            +
                    { @field.name => params }
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class FieldJsonFacet < AbstractJsonFieldFacet
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def initialize(field, options, setup)
         | 
| 6 | 
            +
                    super
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def field_name_with_local_params
         | 
| 10 | 
            +
                    {
         | 
| 11 | 
            +
                      @field.name => {
         | 
| 12 | 
            +
                        type: 'terms',
         | 
| 13 | 
            +
                        field: @field.indexed_name,
         | 
| 14 | 
            +
                      }.merge!(init_params)
         | 
| 15 | 
            +
                    }
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -10,9 +10,42 @@ module Sunspot | |
| 10 10 | 
             
                    @facets << field
         | 
| 11 11 | 
             
                  end
         | 
| 12 12 |  | 
| 13 | 
            +
                  def add_json_facet(json_facet)
         | 
| 14 | 
            +
                    @json_facet = json_facet
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 13 17 | 
             
                  def to_params
         | 
| 14 | 
            -
                    params = { | 
| 15 | 
            -
                     | 
| 18 | 
            +
                    params = {}
         | 
| 19 | 
            +
                    if !@json_facet.nil?
         | 
| 20 | 
            +
                      params['json.facet'] = recursive_add_stats(@json_facet.get_params).to_json
         | 
| 21 | 
            +
                    else
         | 
| 22 | 
            +
                      params.merge!(:stats => true, :"stats.field" => [@field.indexed_name])
         | 
| 23 | 
            +
                      params[facet_key] = @facets.map(&:indexed_name) unless @facets.empty?
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                    params
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  STATS_FUNCTIONS = [:min, :max, :sum, :avg, :sumsq]
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def recursive_add_stats(query)
         | 
| 31 | 
            +
                    query.keys.each do |k|
         | 
| 32 | 
            +
                      if !query[k][:facet].nil?
         | 
| 33 | 
            +
                        query[k][:facet] = recursive_add_stats(query[k][:facet])
         | 
| 34 | 
            +
                      end
         | 
| 35 | 
            +
                      query[k][:facet] ||= {}
         | 
| 36 | 
            +
                      query[k][:sort] = { @options[:sort] => @options[:sort_type]||'desc' } unless @options[:sort].nil?
         | 
| 37 | 
            +
                      query[k][:facet].merge!(json_stats_params)
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                    query
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def json_stats_params
         | 
| 43 | 
            +
                    params = {}
         | 
| 44 | 
            +
                    STATS_FUNCTIONS.each { |s| params[s] = "#{s.to_s}(#{@field.indexed_name})" }
         | 
| 45 | 
            +
                    unless @options[:stats].nil?
         | 
| 46 | 
            +
                      to_remove = STATS_FUNCTIONS - @options[:stats]
         | 
| 47 | 
            +
                      to_remove.map { |s| params.delete(s)}
         | 
| 48 | 
            +
                    end
         | 
| 16 49 | 
             
                    params
         | 
| 17 50 | 
             
                  end
         | 
| 18 51 |  | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Query
         | 
| 3 | 
            +
                class RangeJsonFacet < AbstractJsonFieldFacet
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  SECONDS_IN_DAY = 86400
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(field, options, setup)
         | 
| 8 | 
            +
                    raise Exception.new("Need to specify a range") if options[:range].nil?
         | 
| 9 | 
            +
                    @start = options[:range].first
         | 
| 10 | 
            +
                    @end = options[:range].last
         | 
| 11 | 
            +
                    @gap = options[:gap] || SECONDS_IN_DAY
         | 
| 12 | 
            +
                    super
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def field_name_with_local_params
         | 
| 16 | 
            +
                    {
         | 
| 17 | 
            +
                      @field.name => {
         | 
| 18 | 
            +
                        type: 'range',
         | 
| 19 | 
            +
                        field: @field.indexed_name,
         | 
| 20 | 
            +
                        start: @field.to_indexed(@start),
         | 
| 21 | 
            +
                        end: @field.to_indexed(@end),
         | 
| 22 | 
            +
                        gap: @gap
         | 
| 23 | 
            +
                      }.merge!(init_params)
         | 
| 24 | 
            +
                    }
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
    
        data/lib/sunspot/schema.rb
    CHANGED
    
    | @@ -88,8 +88,16 @@ module Sunspot | |
| 88 88 | 
             
                # Return an XML representation of this schema using the ERB template
         | 
| 89 89 | 
             
                #
         | 
| 90 90 | 
             
                def to_xml
         | 
| 91 | 
            -
                   | 
| 92 | 
            -
                   | 
| 91 | 
            +
                  template_path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'schema.xml.erb')
         | 
| 92 | 
            +
                  template_text = File.read(template_path)
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  erb = if RUBY_VERSION >= '2.6'
         | 
| 95 | 
            +
                    ERB.new(template_text, trim_mode: '-')
         | 
| 96 | 
            +
                  else
         | 
| 97 | 
            +
                    ERB.new(template_text, nil, '-')
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  erb.result(binding)
         | 
| 93 101 | 
             
                end
         | 
| 94 102 |  | 
| 95 103 | 
             
                private
         | 
    
        data/lib/sunspot/search.rb
    CHANGED
    
    | @@ -1,6 +1,7 @@ | |
| 1 | 
            -
            %w(abstract_search standard_search more_like_this_search query_facet | 
| 2 | 
            -
               date_facet range_facet  | 
| 3 | 
            -
                | 
| 1 | 
            +
            %w(abstract_search standard_search more_like_this_search query_facet
         | 
| 2 | 
            +
               field_facet field_json_facet date_facet range_facet json_facet_stats
         | 
| 3 | 
            +
               facet_row json_facet_row hit highlight field_group group hit_enumerable
         | 
| 4 | 
            +
               stats_row stats_json_row field_stats stats_facet query_group).each do |file|
         | 
| 4 5 | 
             
              require File.join(File.dirname(__FILE__), 'search', file)
         | 
| 5 6 | 
             
            end
         | 
| 6 7 |  | 
| @@ -182,6 +182,10 @@ module Sunspot | |
| 182 182 | 
             
                    end
         | 
| 183 183 | 
             
                  end
         | 
| 184 184 |  | 
| 185 | 
            +
                  def json_facet_stats(name, options = {})
         | 
| 186 | 
            +
                    JsonFacetStats.new(name, self, options)
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
             | 
| 185 189 | 
             
                  #
         | 
| 186 190 | 
             
                  # Deprecated in favor of optional second argument to #facet
         | 
| 187 191 | 
             
                  #
         | 
| @@ -193,6 +197,10 @@ module Sunspot | |
| 193 197 | 
             
                    @solr_result['facet_counts']
         | 
| 194 198 | 
             
                  end
         | 
| 195 199 |  | 
| 200 | 
            +
                  def json_facet_response #:nodoc:
         | 
| 201 | 
            +
                    @solr_result['facets']
         | 
| 202 | 
            +
                  end
         | 
| 203 | 
            +
             | 
| 196 204 | 
             
                  def stats_response #:nodoc:
         | 
| 197 205 | 
             
                    @solr_result['stats']['stats_fields']
         | 
| 198 206 | 
             
                  end
         | 
| @@ -255,6 +263,11 @@ module Sunspot | |
| 255 263 | 
             
                    add_stats(field.name, FieldStats.new(field, self))
         | 
| 256 264 | 
             
                  end
         | 
| 257 265 |  | 
| 266 | 
            +
                  def add_json_facet(field, options = {})
         | 
| 267 | 
            +
                    name = (options[:name] || field.name)
         | 
| 268 | 
            +
                    add_facet(name, FieldJsonFacet.new(field, self, options))
         | 
| 269 | 
            +
                  end
         | 
| 270 | 
            +
             | 
| 258 271 | 
             
                  def highlights_for(doc) #:nodoc:
         | 
| 259 272 | 
             
                    if @solr_result['highlighting']
         | 
| 260 273 | 
             
                      @solr_result['highlighting'][doc['id']]
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Search
         | 
| 3 | 
            +
                class FieldJsonFacet
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attr_reader :name
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(field, search, options)
         | 
| 8 | 
            +
                    @name, @search, @options = name, search, options
         | 
| 9 | 
            +
                    @field = field
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def rows
         | 
| 13 | 
            +
                    @rows ||=
         | 
| 14 | 
            +
                      begin
         | 
| 15 | 
            +
                        json_facet_response = @search.json_facet_response[@field.name.to_s]
         | 
| 16 | 
            +
                        data = json_facet_response.nil? ? [] : json_facet_response['buckets']
         | 
| 17 | 
            +
                        rows = []
         | 
| 18 | 
            +
                        data.each do |d|
         | 
| 19 | 
            +
                          rows << JsonFacetRow.new(d, self)
         | 
| 20 | 
            +
                        end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                        if @options[:sort] == :count
         | 
| 23 | 
            +
                          rows.sort! { |lrow, rrow| rrow.count <=> lrow.count }
         | 
| 24 | 
            +
                        else
         | 
| 25 | 
            +
                          rows.sort! { |lrow, rrow| lrow.value <=> rrow.value }
         | 
| 26 | 
            +
                        end
         | 
| 27 | 
            +
                        rows
         | 
| 28 | 
            +
                      end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Search
         | 
| 3 | 
            +
                class JsonFacetRow
         | 
| 4 | 
            +
                  attr_reader :value, :count, :nested
         | 
| 5 | 
            +
                  attr_writer :instance #:nodoc:
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(data, facet) #:nodoc:
         | 
| 8 | 
            +
                    @value = data['val']
         | 
| 9 | 
            +
                    @count = data['distinct'] || data['count']
         | 
| 10 | 
            +
                    @facet = facet
         | 
| 11 | 
            +
                    @nested_key = data.keys.select { |k| data[k].is_a?(Hash) }.first
         | 
| 12 | 
            +
                    @nested = recursive_nested_initialization(data) unless @nested_key.nil?
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  # 
         | 
| 16 | 
            +
                  # Return the instance referenced by this facet row. Only valid for field
         | 
| 17 | 
            +
                  # facets whose fields are defined with the :references key.
         | 
| 18 | 
            +
                  #
         | 
| 19 | 
            +
                  def instance
         | 
| 20 | 
            +
                    if !defined?(@instance)
         | 
| 21 | 
            +
                      @facet.populate_instances
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                    @instance
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def inspect
         | 
| 27 | 
            +
                    "<Sunspot::Search::FacetRow:#{value.inspect} (#{count}) #{nested.nil? ? '' : " nested_count=#{nested.size}"}>"
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  private
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def recursive_nested_initialization(data)
         | 
| 33 | 
            +
                    data[@nested_key]['buckets'].map do |d|
         | 
| 34 | 
            +
                      JsonFacetRow.new(d, @facet)
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Search
         | 
| 3 | 
            +
                class JsonFacetStats
         | 
| 4 | 
            +
                  def initialize(field, search, options)
         | 
| 5 | 
            +
                    @field, @search, @options = field, search, options
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def rows
         | 
| 9 | 
            +
                    @rows ||=
         | 
| 10 | 
            +
                      begin
         | 
| 11 | 
            +
                        json_facet_response = @search.json_facet_response[@field.to_s]
         | 
| 12 | 
            +
                        data = json_facet_response.nil? ? [] : json_facet_response['buckets']
         | 
| 13 | 
            +
                        rows = []
         | 
| 14 | 
            +
                        data.each do |d|
         | 
| 15 | 
            +
                          rows << StatsJsonRow.new(d, nil, d['val'])
         | 
| 16 | 
            +
                        end
         | 
| 17 | 
            +
                        rows
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            module Sunspot
         | 
| 2 | 
            +
              module Search
         | 
| 3 | 
            +
                class StatsJsonRow
         | 
| 4 | 
            +
                  attr_reader :data, :value, :nested
         | 
| 5 | 
            +
                  attr_writer :instance #:nodoc:
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize(data, facet = nil, value = nil) #:nodoc:
         | 
| 8 | 
            +
                    @data, @facet, @value = data, facet, value
         | 
| 9 | 
            +
                    @facet_fields = []
         | 
| 10 | 
            +
                    @nested_key = data.keys.select { |k| data[k].is_a?(Hash) }.first
         | 
| 11 | 
            +
                    @nested = recursive_nested_initialization(data) unless @nested_key.nil?
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def min
         | 
| 15 | 
            +
                    data['min']
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def max
         | 
| 19 | 
            +
                    data['max']
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def count
         | 
| 23 | 
            +
                    data['count']
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def sum
         | 
| 27 | 
            +
                    data['sum']
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def missing
         | 
| 31 | 
            +
                    data['missing']
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def sum_of_squares
         | 
| 35 | 
            +
                    data['sumsq']
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                  alias :sumsq :sum_of_squares
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def mean
         | 
| 40 | 
            +
                    data['avg']
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                  alias :avg :mean
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def standard_deviation
         | 
| 45 | 
            +
                    data['stddev']
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def facet name
         | 
| 49 | 
            +
                    facets.find { |facet| facet.field.name == name.to_sym }
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def facets
         | 
| 53 | 
            +
                    @facets ||= @facet_fields.map do |field|
         | 
| 54 | 
            +
                      StatsFacet.new(field, data['facets'][field.indexed_name])
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def instance
         | 
| 59 | 
            +
                    if !defined?(@instance)
         | 
| 60 | 
            +
                      @facet.populate_instances
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                    @instance
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def inspect
         | 
| 66 | 
            +
                    "<Sunspot::Search::StatsJsonRow:#{value.inspect} min=#{min} max=#{max}"\
         | 
| 67 | 
            +
                    " count=#{count} sum=#{sum} missing=#{missing} sum_of_squares=#{sum_of_squares}"\
         | 
| 68 | 
            +
                    " mean=#{mean} standard_deviation=#{standard_deviation}"\
         | 
| 69 | 
            +
                    " #{nested.nil? ? '' : "nested_count=#{nested.size}"}>"
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  private
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  def recursive_nested_initialization(data)
         | 
| 75 | 
            +
                    data[@nested_key]['buckets'].map do |d|
         | 
| 76 | 
            +
                      StatsJsonRow.new(d, @facet, d['val'])
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         | 
| @@ -59,7 +59,9 @@ module Sunspot | |
| 59 59 | 
             
                  end
         | 
| 60 60 |  | 
| 61 61 | 
             
                  def inspect
         | 
| 62 | 
            -
                    "<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max} | 
| 62 | 
            +
                    "<Sunspot::Search::StatsRow:#{value.inspect} min=#{min} max=#{max}"\
         | 
| 63 | 
            +
                    " count=#{self.count} sum=#{sum} missing=#{missing} sum_of_squares=#{sum_of_squares}"\
         | 
| 64 | 
            +
                    " mean=#{mean} standard_deviation=#{standard_deviation}>"
         | 
| 63 65 | 
             
                  end
         | 
| 64 66 | 
             
                end
         | 
| 65 67 | 
             
              end
         | 
    
        data/lib/sunspot/session.rb
    CHANGED
    
    | @@ -254,11 +254,13 @@ module Sunspot | |
| 254 254 | 
             
                # RSolr::Connection::Base:: The connection for this session
         | 
| 255 255 | 
             
                #
         | 
| 256 256 | 
             
                def connection
         | 
| 257 | 
            -
                  @connection ||=
         | 
| 258 | 
            -
                     | 
| 259 | 
            -
             | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 257 | 
            +
                  @connection ||= self.class.connection_class.connect(
         | 
| 258 | 
            +
                    url: config.solr.url,
         | 
| 259 | 
            +
                    read_timeout: config.solr.read_timeout,
         | 
| 260 | 
            +
                    open_timeout: config.solr.open_timeout,
         | 
| 261 | 
            +
                    proxy: config.solr.proxy,
         | 
| 262 | 
            +
                    update_format: config.solr.update_format || :xml
         | 
| 263 | 
            +
                  )
         | 
| 262 264 | 
             
                end
         | 
| 263 265 |  | 
| 264 266 | 
             
                def indexer
         | 
    
        data/lib/sunspot/util.rb
    CHANGED
    
    | @@ -188,6 +188,29 @@ module Sunspot | |
| 188 188 | 
             
                    RSolr.solr_escape(value).gsub(/([\s\.])/, '\\\\\1')
         | 
| 189 189 | 
             
                  end
         | 
| 190 190 |  | 
| 191 | 
            +
                  def parse_json_facet(field_name, options, setup)
         | 
| 192 | 
            +
                    field = setup.field(field_name)
         | 
| 193 | 
            +
                    if options[:time_range]
         | 
| 194 | 
            +
                      unless field.type.is_a?(Sunspot::Type::TimeType)
         | 
| 195 | 
            +
                        raise(
         | 
| 196 | 
            +
                          ArgumentError,
         | 
| 197 | 
            +
                          ':time_range can only be specified for Date or Time fields'
         | 
| 198 | 
            +
                        )
         | 
| 199 | 
            +
                      end
         | 
| 200 | 
            +
                      Sunspot::Query::DateFieldJsonFacet.new(field, options, setup)
         | 
| 201 | 
            +
                    elsif options[:range]
         | 
| 202 | 
            +
                      unless [Sunspot::Type::TimeType, Sunspot::Type::FloatType, Sunspot::Type::IntegerType ].find{|type| field.type.is_a?(type)}
         | 
| 203 | 
            +
                        raise(
         | 
| 204 | 
            +
                          ArgumentError,
         | 
| 205 | 
            +
                          ':range can only be specified for date or numeric fields'
         | 
| 206 | 
            +
                        )
         | 
| 207 | 
            +
                      end
         | 
| 208 | 
            +
                      Sunspot::Query::RangeJsonFacet.new(field, options, setup)
         | 
| 209 | 
            +
                    else
         | 
| 210 | 
            +
                      Sunspot::Query::FieldJsonFacet.new(field, options, setup)
         | 
| 211 | 
            +
                    end
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
             | 
| 191 214 | 
             
                  private
         | 
| 192 215 |  | 
| 193 216 | 
             
                  #
         | 
    
        data/lib/sunspot/version.rb
    CHANGED
    
    
| @@ -66,7 +66,9 @@ shared_examples_for 'fulltext query' do | |
| 66 66 | 
             
                end
         | 
| 67 67 |  | 
| 68 68 | 
             
                it 'puts default dismax parameters in subquery' do
         | 
| 69 | 
            -
                  expect(subqueries(:q).last[:qf].split(' ').sort).to | 
| 69 | 
            +
                  expect(subqueries(:q).last[:qf].split(' ').sort).to(
         | 
| 70 | 
            +
                    eq(%w(backwards_title_text body_textsv tags_textv text_array_text title_text))
         | 
| 71 | 
            +
                  )
         | 
| 70 72 | 
             
                end
         | 
| 71 73 |  | 
| 72 74 | 
             
                it 'puts field list in main query' do
         | 
| @@ -78,14 +80,18 @@ shared_examples_for 'fulltext query' do | |
| 78 80 | 
             
                search = search do
         | 
| 79 81 | 
             
                  keywords 'keyword search'
         | 
| 80 82 | 
             
                end
         | 
| 81 | 
            -
                expect(connection.searches.last[:qf].split(' ').sort).to | 
| 83 | 
            +
                expect(connection.searches.last[:qf].split(' ').sort).to(
         | 
| 84 | 
            +
                  eq(%w(backwards_title_text body_textsv tags_textv text_array_text title_text))
         | 
| 85 | 
            +
                )
         | 
| 82 86 | 
             
              end
         | 
| 83 87 |  | 
| 84 88 | 
             
              it 'searches both stored and unstored text fields' do
         | 
| 85 89 | 
             
                search Post, Namespaced::Comment do
         | 
| 86 90 | 
             
                  keywords 'keyword search'
         | 
| 87 91 | 
             
                end
         | 
| 88 | 
            -
                expect(connection.searches.last[:qf].split(' ').sort).to | 
| 92 | 
            +
                expect(connection.searches.last[:qf].split(' ').sort).to(
         | 
| 93 | 
            +
                  eq(%w(author_name_text backwards_title_text body_text body_textsv tags_textv text_array_text title_text))
         | 
| 94 | 
            +
                )
         | 
| 89 95 | 
             
              end
         | 
| 90 96 |  | 
| 91 97 | 
             
              it 'searches only specified text fields when specified' do
         | 
| @@ -101,7 +107,7 @@ shared_examples_for 'fulltext query' do | |
| 101 107 | 
             
                    exclude_fields :backwards_title, :body_mlt
         | 
| 102 108 | 
             
                  end
         | 
| 103 109 | 
             
                end
         | 
| 104 | 
            -
                expect(connection.searches.last[:qf].split(' ').sort).to eq(%w(body_textsv tags_textv title_text))
         | 
| 110 | 
            +
                expect(connection.searches.last[:qf].split(' ').sort).to eq(%w(body_textsv tags_textv text_array_text title_text))
         | 
| 105 111 | 
             
              end
         | 
| 106 112 |  | 
| 107 113 | 
             
              it 'assigns boost to fields when specified' do
         | 
| @@ -173,7 +179,9 @@ shared_examples_for 'fulltext query' do | |
| 173 179 | 
             
                    boost_fields :title => 1.5
         | 
| 174 180 | 
             
                  end
         | 
| 175 181 | 
             
                end
         | 
| 176 | 
            -
                expect(connection.searches.last[:qf].split(' ').sort).to | 
| 182 | 
            +
                expect(connection.searches.last[:qf].split(' ').sort).to(
         | 
| 183 | 
            +
                  eq(%w(backwards_title_text body_textsv tags_textv text_array_text title_text^1.5))
         | 
| 184 | 
            +
                )
         | 
| 177 185 | 
             
              end
         | 
| 178 186 |  | 
| 179 187 | 
             
              it 'ignores boost fields that do not apply' do
         | 
| @@ -182,7 +190,9 @@ shared_examples_for 'fulltext query' do | |
| 182 190 | 
             
                    boost_fields :bogus => 1.2, :title => 1.5
         | 
| 183 191 | 
             
                  end
         | 
| 184 192 | 
             
                end
         | 
| 185 | 
            -
                expect(connection.searches.last[:qf].split(' ').sort).to | 
| 193 | 
            +
                expect(connection.searches.last[:qf].split(' ').sort).to(
         | 
| 194 | 
            +
                  eq(%w(backwards_title_text body_textsv tags_textv text_array_text title_text^1.5))
         | 
| 195 | 
            +
                )
         | 
| 186 196 | 
             
              end
         | 
| 187 197 |  | 
| 188 198 | 
             
              it 'sets default boost with default fields' do
         | 
| @@ -2,6 +2,7 @@ module IntegrationHelper | |
| 2 2 | 
             
              def self.included(base)
         | 
| 3 3 | 
             
                base.before(:all) do
         | 
| 4 4 | 
             
                  Sunspot.config.solr.url = ENV['SOLR_URL'] || 'http://localhost:8983/solr/default'
         | 
| 5 | 
            +
                  Sunspot.config.solr.update_format = ENV['UPDATE_FORMAT'].to_sym if ENV['UPDATE_FORMAT']
         | 
| 5 6 | 
             
                  Sunspot.reset!(true)
         | 
| 6 7 | 
             
                end
         | 
| 7 8 | 
             
              end
         | 
| @@ -156,6 +156,136 @@ describe 'search faceting' do | |
| 156 156 | 
             
                end
         | 
| 157 157 | 
             
              end
         | 
| 158 158 |  | 
| 159 | 
            +
              context 'json facet options' do
         | 
| 160 | 
            +
                before :all do
         | 
| 161 | 
            +
                  Sunspot.remove_all
         | 
| 162 | 
            +
                  facet_values = %w(zero one two three four)
         | 
| 163 | 
            +
                  facet_values.each_with_index do |value, i|
         | 
| 164 | 
            +
                    i.times { Sunspot.index(Post.new(:title => value, :blog_id => 1)) }
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                  Sunspot.index(Post.new(:blog_id => 1))
         | 
| 167 | 
            +
                  Sunspot.index(Post.new(:title => 'zero', :blog_id => 2))
         | 
| 168 | 
            +
                  Sunspot.commit
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                it 'should return indexed elements' do
         | 
| 172 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 173 | 
            +
                     json_facet(:title)
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
                  expect(search.facet(:title).rows.size).to eq(5)
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                it 'should limit the number of facet rows' do
         | 
| 179 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 180 | 
            +
                    json_facet :title, :limit => 3
         | 
| 181 | 
            +
                  end
         | 
| 182 | 
            +
                  expect(search.facet(:title).rows.size).to eq(3)
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                it 'should not return zeros by default' do
         | 
| 186 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 187 | 
            +
                    with :blog_id, 1
         | 
| 188 | 
            +
                    json_facet :title
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
                  expect(search.facet(:title).rows.map { |row| row.value }).not_to include('zero')
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                it 'should return a specified minimum count' do
         | 
| 194 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 195 | 
            +
                    with :blog_id, 1
         | 
| 196 | 
            +
                    json_facet :title, :minimum_count => 2
         | 
| 197 | 
            +
                  end
         | 
| 198 | 
            +
                  expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two))
         | 
| 199 | 
            +
                end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                it 'should order facets lexically' do
         | 
| 202 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 203 | 
            +
                    with :blog_id, 1
         | 
| 204 | 
            +
                    json_facet :title, :sort => :index
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                  expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four one three two))
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                it 'should order facets by count' do
         | 
| 210 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 211 | 
            +
                    with :blog_id, 1
         | 
| 212 | 
            +
                    json_facet :title, :sort => :count
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
                  expect(search.facet(:title).rows.map { |row| row.value }).to eq(%w(four three two one))
         | 
| 215 | 
            +
                end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                it 'should limit facet values by prefix' do
         | 
| 218 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 219 | 
            +
                    with :blog_id, 1
         | 
| 220 | 
            +
                    json_facet :title, :prefix => 't'
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
                  expect(search.facet(:title).rows.map { |row| row.value }.sort).to eq(%w(three two))
         | 
| 223 | 
            +
                end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
              end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
              context 'nested json facet' do
         | 
| 228 | 
            +
                before :all do
         | 
| 229 | 
            +
                  Sunspot.remove_all
         | 
| 230 | 
            +
                  facet_values = %w(zero one two three four)
         | 
| 231 | 
            +
                  nested_facet_values = %w(alfa bravo charlie delta)
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                  facet_values.each do |value|
         | 
| 234 | 
            +
                    nested_facet_values.each do |v2|
         | 
| 235 | 
            +
                      Sunspot.index(Post.new(:title => value, :author_name => v2, :blog_id => 1))
         | 
| 236 | 
            +
                    end
         | 
| 237 | 
            +
                  end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                  0.upto(9) { |i| Sunspot.index(Post.new(:title => 'zero', :author_name => "another#{i}", :blog_id => 1)) }
         | 
| 240 | 
            +
                  
         | 
| 241 | 
            +
                  Sunspot.commit
         | 
| 242 | 
            +
                end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                it 'should get nested' do
         | 
| 245 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 246 | 
            +
                    json_facet(:title, nested: { field: :author_name } )
         | 
| 247 | 
            +
                  end
         | 
| 248 | 
            +
                  expect(search.facet(:title).rows.first.nested.size).to eq(4)
         | 
| 249 | 
            +
                end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                it 'without limit take the first 10' do
         | 
| 252 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 253 | 
            +
                    json_facet(:title, nested: { field: :author_name } )
         | 
| 254 | 
            +
                  end
         | 
| 255 | 
            +
                  expect(search.facet(:title).rows.last.nested.size).to eq(10)
         | 
| 256 | 
            +
                end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                it 'without limit' do
         | 
| 259 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 260 | 
            +
                    json_facet(:title, nested: { field: :author_name, limit: -1 } )
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
                  expect(search.facet(:title).rows.last.nested.size).to eq(14)
         | 
| 263 | 
            +
                end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                it 'works with distinct' do
         | 
| 266 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 267 | 
            +
                    json_facet(:title, nested: { field: :author_name, distinct: { strategy: :unique } } )
         | 
| 268 | 
            +
                  end
         | 
| 269 | 
            +
                  expect(search.facet(:title).rows.first.nested.map(&:count).uniq.size).to eq(1)
         | 
| 270 | 
            +
                end
         | 
| 271 | 
            +
             | 
| 272 | 
            +
                it 'should limit the nested facet' do
         | 
| 273 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 274 | 
            +
                    json_facet(:title, nested: { field: :author_name, limit: 2 } )
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
                  expect(search.facet(:title).rows.first.nested.size).to eq(2)
         | 
| 277 | 
            +
                end
         | 
| 278 | 
            +
             | 
| 279 | 
            +
                it 'should work nested of nested' do
         | 
| 280 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 281 | 
            +
                    json_facet(:title, nested: { field: :author_name, nested: { field: :title } } )
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
                  expect(search.facet(:title).rows.first.nested.first.nested.size).to eq(1)
         | 
| 284 | 
            +
                  expect(search.facet(:title).rows.first.nested.first.nested.first.nested).to eq(nil)
         | 
| 285 | 
            +
                end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
              end
         | 
| 288 | 
            +
             | 
| 159 289 | 
             
              context 'prefix escaping' do
         | 
| 160 290 | 
             
                before do
         | 
| 161 291 | 
             
                  Sunspot.remove_all
         | 
| @@ -259,6 +389,57 @@ describe 'search faceting' do | |
| 259 389 | 
             
                end
         | 
| 260 390 | 
             
              end
         | 
| 261 391 |  | 
| 392 | 
            +
              context 'distinct field facets' do
         | 
| 393 | 
            +
                before :all do
         | 
| 394 | 
            +
                  Sunspot.remove_all
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                  Sunspot.index!(
         | 
| 397 | 
            +
                      (0..5).map { |i| Post.new(:blog_id => i, :title => 'title') }
         | 
| 398 | 
            +
                  )
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                  0.upto(3) { |i| Sunspot.index(Post.new(:blog_id => i, :title => 'title')) }
         | 
| 401 | 
            +
             | 
| 402 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 4, :title => 'other title'))
         | 
| 403 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 5, :title => 'other title'))
         | 
| 404 | 
            +
             | 
| 405 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 40, :title => 'title'))
         | 
| 406 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 40, :title => 'title'))
         | 
| 407 | 
            +
             | 
| 408 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 40, :title => 'other title'))
         | 
| 409 | 
            +
                  Sunspot.index!(Post.new(:blog_id => 40, :title => 'other title'))
         | 
| 410 | 
            +
                end
         | 
| 411 | 
            +
             | 
| 412 | 
            +
                it 'should return unique indexed elements for a field' do
         | 
| 413 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 414 | 
            +
                    json_facet(:blog_id, distinct: { strategy: :unique })
         | 
| 415 | 
            +
                  end
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                  expect(search.facet(:blog_id).rows.size).to eq(7)
         | 
| 418 | 
            +
                  expect(search.facet(:blog_id).rows.map(&:count).uniq.size).to eq(1)
         | 
| 419 | 
            +
                end
         | 
| 420 | 
            +
             | 
| 421 | 
            +
                it 'should return unique indexed elements for a field and facet on a field' do
         | 
| 422 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 423 | 
            +
                    json_facet(:blog_id, distinct: { group_by: :title, strategy: :unique })
         | 
| 424 | 
            +
                  end
         | 
| 425 | 
            +
             | 
| 426 | 
            +
                  expect(search.facet(:blog_id).rows.size).to eq(2)
         | 
| 427 | 
            +
                  expect(search.facet(:blog_id).rows[0].count).to eq(3)
         | 
| 428 | 
            +
                  expect(search.facet(:blog_id).rows[1].count).to eq(7)
         | 
| 429 | 
            +
                end
         | 
| 430 | 
            +
             | 
| 431 | 
            +
                it 'should return unique indexed elements for a field and facet on a field with hll' do
         | 
| 432 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 433 | 
            +
                    json_facet(:blog_id, distinct: { group_by: :title, strategy: :hll })
         | 
| 434 | 
            +
                  end
         | 
| 435 | 
            +
             | 
| 436 | 
            +
                  expect(search.facet(:blog_id).rows.size).to eq(2)
         | 
| 437 | 
            +
                  expect(search.facet(:blog_id).rows[0].count).to eq(3)
         | 
| 438 | 
            +
                  expect(search.facet(:blog_id).rows[1].count).to eq(7)
         | 
| 439 | 
            +
                end
         | 
| 440 | 
            +
             | 
| 441 | 
            +
              end
         | 
| 442 | 
            +
             | 
| 262 443 | 
             
              context 'date facets' do
         | 
| 263 444 | 
             
                before :all do
         | 
| 264 445 | 
             
                  Sunspot.remove_all
         | 
| @@ -278,6 +459,38 @@ describe 'search faceting' do | |
| 278 459 | 
             
                  expect(search.facet(:published_at).rows.last.value).to eq((time + 60*60*24)..(time + 60*60*24*2))
         | 
| 279 460 | 
             
                  expect(search.facet(:published_at).rows.last.count).to eq(1)
         | 
| 280 461 | 
             
                end
         | 
| 462 | 
            +
             | 
| 463 | 
            +
                it 'json facet should return time ranges' do
         | 
| 464 | 
            +
                  days_diff = 15
         | 
| 465 | 
            +
                  time_from = Time.utc(2009, 7, 8)
         | 
| 466 | 
            +
                  time_to = Time.utc(2009, 7, 8 + days_diff)
         | 
| 467 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 468 | 
            +
                    json_facet(
         | 
| 469 | 
            +
                        :published_at,
         | 
| 470 | 
            +
                        :time_range => time_from..time_to
         | 
| 471 | 
            +
                    )
         | 
| 472 | 
            +
                  end
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                  expect(search.facet(:published_at).rows.size).to eq(days_diff)
         | 
| 475 | 
            +
                  expect(search.facet(:published_at).rows[0].count).to eq(2)
         | 
| 476 | 
            +
                  expect(search.facet(:published_at).rows[1].count).to eq(1)
         | 
| 477 | 
            +
                end
         | 
| 478 | 
            +
             | 
| 479 | 
            +
                it 'json facet should return time ranges with custom gap' do
         | 
| 480 | 
            +
                  days_diff = 10
         | 
| 481 | 
            +
                  time_from = Time.utc(2009, 7, 8)
         | 
| 482 | 
            +
                  time_to = Time.utc(2009, 7, 8 + days_diff)
         | 
| 483 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 484 | 
            +
                    json_facet(
         | 
| 485 | 
            +
                        :published_at,
         | 
| 486 | 
            +
                        :time_range => time_from..time_to,
         | 
| 487 | 
            +
                        gap: 60*60*24*2
         | 
| 488 | 
            +
                    )
         | 
| 489 | 
            +
                  end
         | 
| 490 | 
            +
                  expect(search.facet(:published_at).rows.size).to eq(days_diff / 2)
         | 
| 491 | 
            +
                  expect(search.facet(:published_at).rows[0].count).to eq(3)
         | 
| 492 | 
            +
                end
         | 
| 493 | 
            +
             | 
| 281 494 | 
             
              end
         | 
| 282 495 |  | 
| 283 496 | 
             
              context 'class facets' do
         | 
| @@ -332,7 +332,7 @@ describe 'scoped_search' do | |
| 332 332 | 
             
                  expect(Sunspot.search(Post) do
         | 
| 333 333 | 
             
                    without(:blog_id, [])
         | 
| 334 334 | 
             
                  end.results).to eq(posts)
         | 
| 335 | 
            -
                end | 
| 335 | 
            +
                end
         | 
| 336 336 |  | 
| 337 337 | 
             
                it 'should return results, ignoring any restriction in a conjunction that has been passed an empty array' do
         | 
| 338 338 | 
             
                  posts = (1..3).map { |i| Post.new(:blog_id => i)}
         | 
| @@ -3,9 +3,9 @@ require File.expand_path('../spec_helper', File.dirname(__FILE__)) | |
| 3 3 | 
             
            describe 'search stats' do
         | 
| 4 4 | 
             
              before :each do
         | 
| 5 5 | 
             
                Sunspot.remove_all
         | 
| 6 | 
            -
                @posts = Post.new(:ratings_average => 4.0, :blog_id => 2),
         | 
| 7 | 
            -
                         Post.new(:ratings_average => 4.0, :blog_id => 1),
         | 
| 8 | 
            -
                         Post.new(:ratings_average => 3.0, :blog_id => 2)
         | 
| 6 | 
            +
                @posts = Post.new(:ratings_average => 4.0, :author_name => 'plinio', :blog_id => 2),
         | 
| 7 | 
            +
                         Post.new(:ratings_average => 4.0, :author_name => 'caio', :blog_id => 1),
         | 
| 8 | 
            +
                         Post.new(:ratings_average => 3.0, :author_name => 'sempronio', :blog_id => 2)
         | 
| 9 9 | 
             
                Sunspot.index!(@posts)
         | 
| 10 10 | 
             
              end
         | 
| 11 11 |  | 
| @@ -44,4 +44,45 @@ describe 'search stats' do | |
| 44 44 | 
             
                  end.stats(:average_rating).facet(:blog_id).rows[1].max).to eq(4.0)
         | 
| 45 45 | 
             
                end
         | 
| 46 46 | 
             
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              describe 'json facets' do
         | 
| 49 | 
            +
                it 'returns minimum on facet row with two blog ids' do
         | 
| 50 | 
            +
                  expect(Sunspot.search(Post) do
         | 
| 51 | 
            +
                    stats :average_rating, sort: :min do
         | 
| 52 | 
            +
                      json_facet :blog_id
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                  end.json_facet_stats(:blog_id).rows[1].min).to eq(3.0)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                it 'returns maximum on facet row with two blog ids' do
         | 
| 58 | 
            +
                  expect(Sunspot.search(Post) do
         | 
| 59 | 
            +
                    stats :average_rating, sort: :max do
         | 
| 60 | 
            +
                      json_facet :blog_id
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end.json_facet_stats(:blog_id).rows[1].max).to eq(4.0)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                it 'returns only sum' do
         | 
| 66 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 67 | 
            +
                    stats :average_rating, stats: [:sum] do
         | 
| 68 | 
            +
                      json_facet :blog_id
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].max).to eq(nil)
         | 
| 72 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].min).to eq(nil)
         | 
| 73 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].avg).to eq(nil)
         | 
| 74 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].sumsq).to eq(nil)
         | 
| 75 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].sum).to eq(4.0)
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                it 'works with nested facets' do
         | 
| 79 | 
            +
                  search = Sunspot.search(Post) do
         | 
| 80 | 
            +
                    stats :average_rating, sort: :min do
         | 
| 81 | 
            +
                      json_facet(:blog_id, nested: { field: :author_name, limit: 3, nested: { field: :average_rating } } )
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                  expect(search.json_facet_stats(:blog_id).rows[1].nested.first.nested.first.min).to eq(4.0)
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              end
         | 
| 47 88 | 
             
            end
         | 
    
        data/spec/mocks/post.rb
    CHANGED
    
    | @@ -37,12 +37,16 @@ end | |
| 37 37 |  | 
| 38 38 | 
             
            Sunspot.setup(Post) do
         | 
| 39 39 | 
             
              text :title, :boost => 2
         | 
| 40 | 
            +
              text :text_array, :boost => 3 do
         | 
| 41 | 
            +
                [title, title]
         | 
| 42 | 
            +
              end
         | 
| 40 43 | 
             
              text :body, :stored => true, :more_like_this => true
         | 
| 41 44 | 
             
              text :backwards_title do
         | 
| 42 45 | 
             
                title.reverse if title
         | 
| 43 46 | 
             
              end
         | 
| 44 47 | 
             
              text :tags, :more_like_this => true
         | 
| 45 48 | 
             
              string :title, :stored => true
         | 
| 49 | 
            +
              string :author_name
         | 
| 46 50 | 
             
              integer :blog_id, :references => Blog
         | 
| 47 51 | 
             
              integer :category_ids, :multiple => true
         | 
| 48 52 | 
             
              float :average_rating, :using => :ratings_average, :trie => true
         | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    | @@ -23,14 +23,10 @@ RSpec.configure do |config| | |
| 23 23 | 
             
              config.order = 'random'
         | 
| 24 24 |  | 
| 25 25 | 
             
              # Mock session available to all spec/api tests
         | 
| 26 | 
            -
              config.include MockSessionHelper,
         | 
| 27 | 
            -
                             :type => :api,
         | 
| 28 | 
            -
                             :file_path => /spec[\\\/]api/
         | 
| 26 | 
            +
              config.include MockSessionHelper, type: :api, file_path: /spec[\\\/]api/
         | 
| 29 27 |  | 
| 30 28 | 
             
              # Real Solr instance is available to integration tests
         | 
| 31 | 
            -
              config.include IntegrationHelper,
         | 
| 32 | 
            -
                             :type => :integration,
         | 
| 33 | 
            -
                             :file_path => /spec[\\\/]integration/
         | 
| 29 | 
            +
              config.include IntegrationHelper, type: :integration, file_path: /spec[\\\/]integration/
         | 
| 34 30 |  | 
| 35 31 | 
             
              # Nested under spec/api
         | 
| 36 32 | 
             
              [:indexer, :query, :search].each do |spec_type|
         | 
    
        data/sunspot.gemspec
    CHANGED
    
    | @@ -31,7 +31,7 @@ Gem::Specification.new do |s| | |
| 31 31 | 
             
              s.add_dependency 'pr_geohash', '~>1.0'
         | 
| 32 32 |  | 
| 33 33 | 
             
              s.add_development_dependency 'rake', '< 12.3'
         | 
| 34 | 
            -
              s.add_development_dependency 'rspec'
         | 
| 34 | 
            +
              s.add_development_dependency 'rspec', '~> 3.7'
         | 
| 35 35 | 
             
              s.add_development_dependency 'appraisal', '2.2.0'
         | 
| 36 36 |  | 
| 37 37 | 
             
              s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' <<
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: sunspot
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 2. | 
| 4 | 
            +
              version: 2.3.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Mat Brown
         | 
| @@ -30,7 +30,7 @@ authors: | |
| 30 30 | 
             
            autorequire: 
         | 
| 31 31 | 
             
            bindir: bin
         | 
| 32 32 | 
             
            cert_chain: []
         | 
| 33 | 
            -
            date: 2018- | 
| 33 | 
            +
            date: 2018-04-08 00:00:00.000000000 Z
         | 
| 34 34 | 
             
            dependencies:
         | 
| 35 35 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 36 36 | 
             
              name: rsolr
         | 
| @@ -84,16 +84,16 @@ dependencies: | |
| 84 84 | 
             
              name: rspec
         | 
| 85 85 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 86 | 
             
                requirements:
         | 
| 87 | 
            -
                - - " | 
| 87 | 
            +
                - - "~>"
         | 
| 88 88 | 
             
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            -
                    version: ' | 
| 89 | 
            +
                    version: '3.7'
         | 
| 90 90 | 
             
              type: :development
         | 
| 91 91 | 
             
              prerelease: false
         | 
| 92 92 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 93 | 
             
                requirements:
         | 
| 94 | 
            -
                - - " | 
| 94 | 
            +
                - - "~>"
         | 
| 95 95 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            -
                    version: ' | 
| 96 | 
            +
                    version: '3.7'
         | 
| 97 97 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 98 98 | 
             
              name: appraisal
         | 
| 99 99 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -159,14 +159,17 @@ files: | |
| 159 159 | 
             
            - lib/sunspot/query.rb
         | 
| 160 160 | 
             
            - lib/sunspot/query/abstract_field_facet.rb
         | 
| 161 161 | 
             
            - lib/sunspot/query/abstract_fulltext.rb
         | 
| 162 | 
            +
            - lib/sunspot/query/abstract_json_field_facet.rb
         | 
| 162 163 | 
             
            - lib/sunspot/query/bbox.rb
         | 
| 163 164 | 
             
            - lib/sunspot/query/boost_query.rb
         | 
| 164 165 | 
             
            - lib/sunspot/query/common_query.rb
         | 
| 165 166 | 
             
            - lib/sunspot/query/composite_fulltext.rb
         | 
| 166 167 | 
             
            - lib/sunspot/query/connective.rb
         | 
| 167 168 | 
             
            - lib/sunspot/query/date_field_facet.rb
         | 
| 169 | 
            +
            - lib/sunspot/query/date_field_json_facet.rb
         | 
| 168 170 | 
             
            - lib/sunspot/query/dismax.rb
         | 
| 169 171 | 
             
            - lib/sunspot/query/field_facet.rb
         | 
| 172 | 
            +
            - lib/sunspot/query/field_json_facet.rb
         | 
| 170 173 | 
             
            - lib/sunspot/query/field_list.rb
         | 
| 171 174 | 
             
            - lib/sunspot/query/field_stats.rb
         | 
| 172 175 | 
             
            - lib/sunspot/query/filter.rb
         | 
| @@ -182,6 +185,7 @@ files: | |
| 182 185 | 
             
            - lib/sunspot/query/pagination.rb
         | 
| 183 186 | 
             
            - lib/sunspot/query/query_facet.rb
         | 
| 184 187 | 
             
            - lib/sunspot/query/range_facet.rb
         | 
| 188 | 
            +
            - lib/sunspot/query/range_json_facet.rb
         | 
| 185 189 | 
             
            - lib/sunspot/query/restriction.rb
         | 
| 186 190 | 
             
            - lib/sunspot/query/scope.rb
         | 
| 187 191 | 
             
            - lib/sunspot/query/sort.rb
         | 
| @@ -197,11 +201,14 @@ files: | |
| 197 201 | 
             
            - lib/sunspot/search/facet_row.rb
         | 
| 198 202 | 
             
            - lib/sunspot/search/field_facet.rb
         | 
| 199 203 | 
             
            - lib/sunspot/search/field_group.rb
         | 
| 204 | 
            +
            - lib/sunspot/search/field_json_facet.rb
         | 
| 200 205 | 
             
            - lib/sunspot/search/field_stats.rb
         | 
| 201 206 | 
             
            - lib/sunspot/search/group.rb
         | 
| 202 207 | 
             
            - lib/sunspot/search/highlight.rb
         | 
| 203 208 | 
             
            - lib/sunspot/search/hit.rb
         | 
| 204 209 | 
             
            - lib/sunspot/search/hit_enumerable.rb
         | 
| 210 | 
            +
            - lib/sunspot/search/json_facet_row.rb
         | 
| 211 | 
            +
            - lib/sunspot/search/json_facet_stats.rb
         | 
| 205 212 | 
             
            - lib/sunspot/search/more_like_this_search.rb
         | 
| 206 213 | 
             
            - lib/sunspot/search/paginated_collection.rb
         | 
| 207 214 | 
             
            - lib/sunspot/search/query_facet.rb
         | 
| @@ -209,6 +216,7 @@ files: | |
| 209 216 | 
             
            - lib/sunspot/search/range_facet.rb
         | 
| 210 217 | 
             
            - lib/sunspot/search/standard_search.rb
         | 
| 211 218 | 
             
            - lib/sunspot/search/stats_facet.rb
         | 
| 219 | 
            +
            - lib/sunspot/search/stats_json_row.rb
         | 
| 212 220 | 
             
            - lib/sunspot/search/stats_row.rb
         | 
| 213 221 | 
             
            - lib/sunspot/session.rb
         | 
| 214 222 | 
             
            - lib/sunspot/session_proxy.rb
         | 
| @@ -348,7 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 348 356 | 
             
                  version: '0'
         | 
| 349 357 | 
             
            requirements: []
         | 
| 350 358 | 
             
            rubyforge_project: sunspot
         | 
| 351 | 
            -
            rubygems_version: 2.6 | 
| 359 | 
            +
            rubygems_version: 2.7.6
         | 
| 352 360 | 
             
            signing_key: 
         | 
| 353 361 | 
             
            specification_version: 4
         | 
| 354 362 | 
             
            summary: Library for expressive, powerful interaction with the Solr search engine
         |