stub_solr 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bfdc89260913319a1d4c3939ee3dfba95feae29d
4
- data.tar.gz: 3ebd59479e416bb97557a990302b310eb3e6636a
3
+ metadata.gz: 3d7ccf1ca8364cbe78d549c7adee20fc57e6657b
4
+ data.tar.gz: e7373b1aad31088dd937d28cf6c7559acc1bc1fb
5
5
  SHA512:
6
- metadata.gz: d763e18b42519ef78114699d73b4f95c537210db9e10934ef9253410f154fcb7c4ff2c0434a96fe478e0220af37ed4b56a4ad7647041330de3ce085991ccd2a6
7
- data.tar.gz: f0d4c8fb320660ace148fb9b9b626d8c8adfb042df3f7f5b82f4b2c6d7274f0cae4be36424ce75e35d3469a6bdb8175da4792c2b36f9714a42f18cd0a49541f2
6
+ metadata.gz: c63a06b47829d41038bb6f581742ba546ea37aa3a0e12d5c9accadd8a371abae23b04c8c5daf860eb753ad30397b2c95d204404227d5c9e066bdeef5e60c6ab2
7
+ data.tar.gz: cd6bfc624908d13a46bdbc8d53c3f350f4823eed7c3a3e4ca5535e21a4d600a931fd66c30bf81fc189518e17612822fdaf79c239bf7e25493bf0ff87e9813536
data/.codeclimate.yml ADDED
@@ -0,0 +1,23 @@
1
+ engines:
2
+ duplication:
3
+ enabled: true
4
+ config:
5
+ languages:
6
+ - ruby
7
+ fixme:
8
+ enabled: true
9
+ rubocop:
10
+ enabled: true
11
+ ratings:
12
+ paths:
13
+ - "**.js"
14
+ - "**.jsx"
15
+ - "**.module"
16
+ - "**.rb"
17
+ exclude_paths:
18
+ - test/
19
+ - Rakefile
20
+ - gemfiles/
21
+ - dev_tasks/
22
+ - bin/
23
+ - tmp/
data/.rubocop.yml CHANGED
@@ -6,6 +6,7 @@ AllCops:
6
6
  - gemfiels/*
7
7
  - test/**/*
8
8
  - tmp/**/*
9
+ - Rakefile
9
10
  TargetRubyVersion: 2.3
10
11
 
11
12
  Rails:
@@ -25,3 +26,6 @@ Style/EmptyLinesAroundClassBody:
25
26
 
26
27
  Style/EmptyLinesAroundMethodBody:
27
28
  Enabled: false
29
+
30
+ Style/FrozenStringLiteralComment:
31
+ Enabled: false
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  `Sunspot.session = Sunspot::Rails::StubSessionProxy.new(Sunspot.session)` is enough for most of cases but if you want more than `allow_any_instance_of(Sunspot::Rails::StubSessionProxy::Search).to receive(:results).and_return(myExpectedResults)` this gem can be helpful. `kaminari` for pagination and plain AR to mimic resutls.
7
7
 
8
- this gem depends on `2.2.6` version of sunspot, sunspot_rails.
8
+ this gem depends on `2.2.x` version of sunspot, sunspot_rails.
9
9
 
10
10
  ## Installation
11
11
 
@@ -15,17 +15,49 @@ Add this line to your application's Gemfile:
15
15
  gem 'stub_solr'
16
16
  ```
17
17
 
18
- And then execute:
19
-
20
- $ bundle
21
-
22
- Or install it yourself as:
18
+ ## Usage
23
19
 
24
- $ gem install stub_solr
20
+ `test_helper` file should have `require 'stub_solr'`. Given a sunspot search block as following codes,
25
21
 
26
- ## Usage
22
+ ```ruby
23
+ class ActivitiesController < ApplicationController
24
+ def search
25
+ ...
26
+ search = Sunspot.search Excercise, Activity do
27
+ any_of do
28
+ with :blocked, false
29
+ without :exhausted, true
30
+ end
31
+
32
+ if min && max
33
+ any_of do
34
+ all_of do
35
+ with :start_limit_number, nil
36
+ with(:end_limit_number).less_than_or_equal_to max
37
+ end
38
+ all_of do
39
+ with :end_limit_number, nil
40
+ with(:start_limit_number).greater_than_or_equal_to min
41
+ end
42
+ all_of do
43
+ without(:start_limit_number, nil)
44
+ without(:end_limit_number, nil)
45
+ with(:start_limit_number).greater_than_or_equal_to min
46
+ with(:end_limit_number).less_than_or_equal_to max
47
+ end
48
+ end
49
+ end
50
+ ## search by text
51
+ fulltext params[:search] if params[:search].present?
52
+ order_by :created_at, :asc
53
+ paginate page: page, per_page: per_page
54
+ end
55
+ ...
56
+ end
57
+ end
58
+ ```
27
59
 
28
- `test_helper` file should have `require 'stub_solr'`. In your test file, add setup and teardown like this
60
+ You can write expected results like this. use `concat` for `any_of` block and `array_1 & array_2` for `all_of` block.
29
61
 
30
62
  ```ruby
31
63
  class ActivitiesControllerTest < ActionDispatch::IntegrationTest
@@ -41,9 +73,15 @@ class ActivitiesControllerTest < ActionDispatch::IntegrationTest
41
73
  super
42
74
  end
43
75
 
44
- test 'search with filter' do
45
- get search_activities_url(params: { visible: true, search: 'beer' })
46
- expected = Activity.where(visible: true).where("title LIKE ?", "%beer%")
76
+ test 'filter with number range' do
77
+ pool_1 = Activity.where(blocked: false).to_a.concat(Activity.where(exhausted: false).to_a).uniq
78
+ target_1 = Activity.where("end_limit_number <= ? AND start_limit_number = ?", 18, nil).to_a
79
+ target_2 = Activity.where("start_limit_number >= ? AND end_limit_number = ?", 3, nil).to_a
80
+ target_3 = Activity.where("start_limit_number >= ?", 3).where("end_limit_number <= ?", 18).to_a
81
+ pool_2 = target_1.concat(target_2).concat(target_3)
82
+ expected = pool_1 & pool_2
83
+
84
+ get search_activities_url(params: { limits: [3, 18] })
47
85
  res = ActiveSupport::JSON.decode(response.body)["activities"]
48
86
  res.size.must_equal expected.size
49
87
  end
@@ -0,0 +1,144 @@
1
+ class Sunspot::Rails::StubSessionProxy
2
+ # with and fulltext can be called multiple times
3
+ # based on the block. that's why i am mutating @results
4
+ #
5
+ # for all and any block, we need to consult
6
+ # sunspot/lib/sunspot/dsl/standard_query.rb
7
+ #
8
+ class Search
9
+ def hits
10
+ self
11
+ end
12
+
13
+ # Added to pass .group_by
14
+ # Idea is save grouped hash result seperately and then
15
+ # access it with [] method.
16
+ #
17
+ # initial_grouped_hits = initial.hits.group_by(&:class_name)
18
+ # initial_grouped_hits["Activity"]
19
+ def [](key)
20
+ return if @grouped_results.empty? || @grouped_results[key].nil?
21
+ @group_key = key
22
+ self
23
+ end
24
+
25
+ # possible situation
26
+ #
27
+ # initial_grouped_hits = initial.hits.group_by(&:class_name)
28
+ # initial_grouped_hits["Activity"].map(&:primary_key)
29
+ def map(&_pr)
30
+ return unless @grouped_results[@group_key]
31
+ @grouped_results[@group_key].map(&:id)
32
+ end
33
+
34
+ # possible situation
35
+ #
36
+ # initial_grouped_hits = initial.hits.group_by(&:class_name)
37
+ def group_by(_ = nil)
38
+ return self if final_results.empty?
39
+ @types.each do |type|
40
+ @grouped_results[type.name] = sorted_temp_result.group_by do |i|
41
+ i.class.name == type.name
42
+ end[true]
43
+ end
44
+ self
45
+ end
46
+
47
+ def phrase_fields(arg)
48
+ @operation_context.first[:textsearch_priorities] = arg
49
+ return self if @operation_context.first[:search_term].empty?
50
+ fulltext(@operation_context.first[:search_term])
51
+ end
52
+
53
+ def query_phrase_slop(_)
54
+ fulltext(@operation_context.first[:search_term])
55
+ end
56
+
57
+ # with default `StubSessionProxy`, the results is blank whatever
58
+ # argument or options you put in the search block
59
+ def fulltext(term, _opt = {}, &bl)
60
+ matches = []
61
+ @operation_context.first[:search_term] = term
62
+ Sunspot::Util::ContextBoundDelegate
63
+ .instance_eval_with_context(self, &bl) unless bl.nil?
64
+ # if there is phrase_fields option then we only search for the field
65
+ # to mimic the priority search. #string_text_fields
66
+ @types.each do |type|
67
+ string_text_fields(type).each do |field|
68
+ if type.has_attribute?(field.to_sym)
69
+ matches << type.where(type.arel_table[field.to_sym].
70
+ matches("%#{term}%")).to_a
71
+ end
72
+ end
73
+ end
74
+ calculated = (result_for_current_context & matches.flatten.uniq)
75
+ operation_context_result(calculated)
76
+ self
77
+ end
78
+
79
+ def order_by(attribute, direction)
80
+ @operation_context.first[:order_key] = attribute
81
+ @operation_context.first[:order_direction] = direction
82
+ self
83
+ end
84
+
85
+ def in_radius(*args)
86
+ calculated = result_for_current_context.select do |r|
87
+ distance = r&.location&.distance_from([args[0], args[1]])
88
+ distance && distance < args[2].to_i
89
+ end
90
+ operation_context_result(calculated)
91
+ self
92
+ end
93
+
94
+ def greater_than(time)
95
+ calculated = filter(result_for_current_context) do |x|
96
+ x.read_attribute(@range_search_field) > time
97
+ end
98
+ operation_context_result(calculated)
99
+ self
100
+ end
101
+
102
+ def greater_than_or_equal_to(time)
103
+ calculated = filter(result_for_current_context) do |x|
104
+ x.read_attribute(@range_search_field) >= time
105
+ end
106
+ operation_context_result(calculated)
107
+ self
108
+ end
109
+
110
+ def less_than(time)
111
+ calculated = filter(result_for_current_context) do |x|
112
+ x.read_attribute(@range_search_field) < time
113
+ end
114
+ operation_context_result(calculated)
115
+ self
116
+ end
117
+
118
+ def less_than_or_equal_to(time)
119
+ calculated = filter(result_for_current_context) do |x|
120
+ x.read_attribute(@range_search_field) <= time
121
+ end
122
+ operation_context_result(calculated)
123
+ self
124
+ end
125
+
126
+ def paginate(args = {})
127
+ @operation_context.first[:page] = args[:page]
128
+ @operation_context.first[:per_page] = args[:page]
129
+ self
130
+ end
131
+
132
+ def total
133
+ @operation_context.last[:result].size
134
+ end
135
+
136
+ def filter(array)
137
+ array.select do |i|
138
+ i.read_attribute(@range_search_field) &&
139
+ !i.read_attribute(@range_search_field).nil? &&
140
+ yield(i)
141
+ end.uniq
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,107 @@
1
+ require File.expand_path('search_helper', File.dirname(__FILE__))
2
+ require File.expand_path('extra_dsl', File.dirname(__FILE__))
3
+ class Sunspot::Rails::StubSessionProxy
4
+ # with and fulltext can be called multiple times
5
+ # based on the block. that's why i am mutating @results
6
+ #
7
+ # for all and any block, we need to consult
8
+ # sunspot/lib/sunspot/dsl/standard_query.rb
9
+ #
10
+ class Search
11
+ attr_reader :block, :types, :page, :per_page, :grouped_results
12
+
13
+ def initialize(results = [], types = [], &block)
14
+ @types = types
15
+ @grouped_results= {}
16
+ @group_key = ''
17
+ @range_search_field = nil
18
+ @operation_context = [{
19
+ operation: 'all', result: results, order_key: nil, order_direction: nil,
20
+ page: 1, per_page: 30, search_term: '', textsearch_priorities: {}
21
+ }]
22
+ @current_index = 0
23
+ run_proc_block(&block)
24
+ end
25
+
26
+ def final_results
27
+ @operation_context.last[:result]
28
+ end
29
+
30
+ def results
31
+ return PaginatedCollection.new if sorted_temp_result.empty?
32
+ if final_results.is_a? Kaminari::PaginatableArray
33
+ return sorted_temp_result
34
+ end
35
+ Kaminari.paginate_array(sorted_temp_result).
36
+ page(@operation_context.first[:page]).
37
+ per(@operation_context.first[:per_page])
38
+ end
39
+
40
+ def any_of(&bl)
41
+ previous_result = result_for_current_context
42
+ push(previous_result, :any)
43
+ Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
44
+ after_results = temp_result_for_current_context
45
+ pop unless @current_index == 0
46
+ # parent is now current context
47
+ if parent_temp_result
48
+ @operation_context[@current_index][:temp_result] =
49
+ parent_temp_result.concat(after_results)
50
+ else
51
+ @operation_context[@current_index][:result] =
52
+ after_results
53
+ end
54
+ self
55
+ end
56
+
57
+ def all_of(&bl)
58
+ previous_result = result_for_current_context
59
+ push(previous_result, :all)
60
+ Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
61
+ after_results = result_for_current_context
62
+ parent_temp_result =
63
+ @operation_context[@current_index - 1][:temp_result]
64
+ pop unless @current_index == 0
65
+ # parent is now current context
66
+ if parent_temp_result
67
+ @operation_context[@current_index][:temp_result] =
68
+ parent_temp_result.concat(after_results)
69
+ else
70
+ @operation_context[@current_index][:result] =
71
+ (previous_result & after_results).uniq
72
+ end
73
+ self
74
+ end
75
+
76
+ def with(att, val = 'no_value')
77
+ # return_early_for_with_blocks(att, val, :with)
78
+ return self if range_no_value?(att, val) && skip_it(att, val)
79
+ return self if range_value_nil?(val, att) && deal_with_nil(att, :with)
80
+ return self if val == 'no_value' || att == :location
81
+ return self if (att == :search_class) && filter_class
82
+ matches = if val.class.name == 'Array'
83
+ select_result_for_array_val(att)
84
+ elsif @types.map{ |x| x.has_attribute?(att)}.include?(true)
85
+ select_result_attr_is_val(att, val)
86
+ else
87
+ result_for_current_context.select { |x| x.send(att) == val }
88
+ end
89
+ operation_context_result(matches)
90
+ self
91
+ end
92
+
93
+ def without(att, val = 'no_value')
94
+ # return_early_for_with_blocks(att, val, :without)
95
+ return self if range_no_value?(att, val) && skip_it(att, val)
96
+ return self if range_value_nil?(val, att) && deal_with_nil(att, :without)
97
+ return self if val == 'no_value' || att == :location
98
+ matches = if val.class.name == 'Array'
99
+ select_result_attr_cant_send_val(att, val)
100
+ else
101
+ select_result_attr_is_not_value(att, val)
102
+ end
103
+ operation_context_result(matches)
104
+ self
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,168 @@
1
+ # serach helper
2
+
3
+ class Sunspot::Rails::StubSessionProxy
4
+ # with and fulltext can be called multiple times
5
+ # based on the block. that's why i am mutating @results
6
+ #
7
+ # for all and any block, we need to consult
8
+ # sunspot/lib/sunspot/dsl/standard_query.rb
9
+ #
10
+ class Search
11
+
12
+ private
13
+
14
+ def instance_eval_with_context(&block)
15
+ Sunspot::Util::ContextBoundDelegate.
16
+ instance_eval_with_context(self, &block)
17
+ end
18
+
19
+ # iterate the given block and change @operation_context
20
+ # block may cotain with, without, fulltext, paginate, in_raius, order_by
21
+ def run_proc_block(&block)
22
+ result = @operation_context.last[:result]
23
+ return PaginatedCollection.new if result.empty?
24
+ instance_eval_with_context(&block)
25
+ self
26
+ end
27
+
28
+ def current_operation
29
+ @operation_context[@current_index][:operation]
30
+ end
31
+
32
+ def result_for_current_context
33
+ @operation_context[@current_index][:result]
34
+ end
35
+
36
+ def temp_result_for_current_context
37
+ @operation_context[@current_index][:temp_result]
38
+ end
39
+
40
+ def parent_temp_result
41
+ @operation_context[@current_index - 1][:temp_result]
42
+ end
43
+
44
+ def operation_context_result(after_results)
45
+ previous_result = result_for_current_context
46
+ unless current_operation == 'any'
47
+ return @operation_context[@current_index][:result] =
48
+ (previous_result & after_results).uniq
49
+ end
50
+ @operation_context[@current_index][:result] =
51
+ previous_result.concat(after_results).uniq
52
+ if temp_result_for_current_context
53
+ @operation_context[@current_index][:temp_result] =
54
+ temp_result_for_current_context.concat(after_results)
55
+ end
56
+ end
57
+
58
+ def pop
59
+ @current_index -= 1
60
+ @operation_context.pop
61
+ end
62
+
63
+ def push(data, type)
64
+ if type == :any
65
+ @operation_context.push(operation: 'any', result: data, temp_result: [])
66
+ else
67
+ @operation_context.push(operation: 'all', result: data)
68
+ end
69
+ @current_index += 1
70
+ end
71
+
72
+ def filter_class
73
+ matches = result_for_current_context.select { |x| x.class.name == value }
74
+ operation_context_result(matches)
75
+ end
76
+
77
+ def range_value_nil?(value, attribute)
78
+ value.nil? && attribute_is_about_range?(attribute)
79
+ end
80
+
81
+ def range_no_value?(attribute, value)
82
+ (value == 'no_value') && attribute_is_about_range?(attribute)
83
+ end
84
+
85
+ def skip_it(attribute, value)
86
+ matches = result_for_current_context.select do |x|
87
+ x.has_attribute?(attribute)
88
+ end
89
+ operation_context_result(matches)
90
+ end
91
+
92
+ def deal_with_nil(attribute, operation)
93
+ matches = if operation == :with
94
+ selelct_result_for_nil(attribute)
95
+ else
96
+ select_result_for_not_nil(attribute)
97
+ end
98
+ operation_context_result(matches)
99
+ end
100
+
101
+ def string_text_fields(type)
102
+ pr = @operation_context.first[:textsearch_priorities]
103
+ return pr.keys unless pr.keys.empty?
104
+ type.columns_hash.select { |k,v| [:string, :text].include? v.type }.keys
105
+ end
106
+
107
+ def attribute_is_about_range?(attribute)
108
+ matched = nil
109
+ Sunspot.session.range_fields.each do |x|
110
+ if attribute.to_s.include? x
111
+ matched = true
112
+ @range_search_field = attribute
113
+ next
114
+ end
115
+ end
116
+ matched
117
+ end
118
+
119
+ def sorted_temp_result
120
+ key = @operation_context.first[:order_key]
121
+ direction = @operation_context.first[:order_direction]
122
+ return final_results unless direction
123
+ asc = final_results.sort_by { |x| x[key] }
124
+ return asc if direction.to_s.downcase.include?('asc')
125
+ asc.reverse
126
+ end
127
+
128
+ def selelct_result_for_nil(attribute)
129
+ result_for_current_context.select do |x|
130
+ x.has_attribute?(attribute) &&
131
+ x.read_attribute(attribute).nil?
132
+ end
133
+ end
134
+
135
+ def select_result_for_not_nil(attribute)
136
+ result_for_current_context.select do |x|
137
+ x.has_attribute?(attribute) &&
138
+ !x.read_attribute(attribute).nil?
139
+ end
140
+ end
141
+
142
+ def select_result_for_array_val(attribute)
143
+ result_for_current_context.select do |x|
144
+ value.include?(x.send(attribute))
145
+ end
146
+ end
147
+
148
+ def select_result_attr_is_val(attribute, value)
149
+ result_for_current_context.select do |x|
150
+ x.read_attribute(attribute) == value
151
+ end
152
+ end
153
+
154
+ def select_result_attr_cant_send_val(attribute, value)
155
+ result_for_current_context.select do |x|
156
+ x.has_attribute?(attribute) && !value.include?(x.send(attribute))
157
+ end
158
+ end
159
+
160
+ def select_result_attr_is_not_value(attribute, value)
161
+ result_for_current_context.select do |x|
162
+ x.has_attribute?(attribute) &&
163
+ !x.read_attribute(attribute).nil? &&
164
+ (x.read_attribute(attribute) != value)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path('search', File.dirname(__FILE__))
2
+ # stub solr with "dara" array for testing.
3
+ # if there is no data then it will behave like default blank search
4
+ # this stub uses Kaminari for pagination
5
+ # once the data is given, solr search block will call search
6
+ # and that will create a new Search with arguments.
7
+ # With the search, it passes the given data as @operation_context.
8
+ # And "run_proc_block" method will go through each search conditions
9
+ # in the block. while going through the block, it will change @operation_context
10
+ #
11
+ class Sunspot::Rails::StubSessionProxy
12
+ attr_reader :block, :data, :range_fields
13
+
14
+ def initialize(original_session, data = [])
15
+ @original_session = original_session
16
+ @data = data
17
+ @range_fields = %w[time month created_at start end]
18
+ end
19
+
20
+ def set_range_fields(arr)
21
+ @range_fields = arr
22
+ end
23
+
24
+ def search(*types, &block)
25
+ Search.new(@data, types, &block)
26
+ end
27
+ end
@@ -1,3 +1,3 @@
1
1
  module StubSolr
2
- VERSION = "0.0.5"
2
+ VERSION = '0.0.6'
3
3
  end
data/lib/stub_solr.rb CHANGED
@@ -1,9 +1,14 @@
1
+ ## requires all necessary fiels
1
2
  require 'kaminari'
3
+ ##
4
+ # previous_page method doesn't exist in Kaminari
5
+ # but we use it for pagination
2
6
  module Kaminari::PageScopeMethods
3
7
  alias_method :previous_page, :prev_page
4
8
  end
5
9
  require 'sunspot'
6
10
  require 'sunspot_rails'
7
11
  require 'rails'
8
- require "stub_solr/version"
9
- require File.expand_path('stub_solr/sunspot_rails_stub_session_proxy', File.dirname(__FILE__))
12
+ require 'stub_solr/version'
13
+ require File.expand_path('stub_solr/stub_session_proxy',
14
+ File.dirname(__FILE__))
data/stub_solr.gemspec CHANGED
@@ -20,9 +20,7 @@ Gem::Specification.new do |gem|
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
21
 
22
22
  gem.add_dependency 'kaminari', '~> 0.17.0'
23
- gem.add_dependency 'sunspot', '~> 2.2.6'
24
- gem.add_dependency 'sunspot_solr', '~> 2.2.6'
25
- gem.add_dependency 'sunspot_rails', '~> 2.2.6'
23
+ gem.add_dependency 'sunspot_rails', '~> 2.2.5'
26
24
  gem.add_dependency 'rails', '>= 4'
27
25
 
28
26
  # gem.add_development_dependency 'simplecov', '~> 0.12.0'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stub_solr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaigouk Kim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-13 00:00:00.000000000 Z
11
+ date: 2016-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kaminari
@@ -24,48 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.17.0
27
- - !ruby/object:Gem::Dependency
28
- name: sunspot
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 2.2.6
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 2.2.6
41
- - !ruby/object:Gem::Dependency
42
- name: sunspot_solr
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 2.2.6
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 2.2.6
55
27
  - !ruby/object:Gem::Dependency
56
28
  name: sunspot_rails
57
29
  requirement: !ruby/object:Gem::Requirement
58
30
  requirements:
59
31
  - - "~>"
60
32
  - !ruby/object:Gem::Version
61
- version: 2.2.6
33
+ version: 2.2.5
62
34
  type: :runtime
63
35
  prerelease: false
64
36
  version_requirements: !ruby/object:Gem::Requirement
65
37
  requirements:
66
38
  - - "~>"
67
39
  - !ruby/object:Gem::Version
68
- version: 2.2.6
40
+ version: 2.2.5
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: rails
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -129,6 +101,7 @@ executables: []
129
101
  extensions: []
130
102
  extra_rdoc_files: []
131
103
  files:
104
+ - ".codeclimate.yml"
132
105
  - ".gitignore"
133
106
  - ".rubocop.yml"
134
107
  - Gemfile
@@ -141,7 +114,10 @@ files:
141
114
  - gemfiles/.bundle/config
142
115
  - gemfiles/rails-5.0.0.1
143
116
  - lib/stub_solr.rb
144
- - lib/stub_solr/sunspot_rails_stub_session_proxy.rb
117
+ - lib/stub_solr/extra_dsl.rb
118
+ - lib/stub_solr/search.rb
119
+ - lib/stub_solr/search_helper.rb
120
+ - lib/stub_solr/stub_session_proxy.rb
145
121
  - lib/stub_solr/version.rb
146
122
  - stub_solr.gemspec
147
123
  homepage: https://github.com/jaigouk/stub_solr
@@ -1,363 +0,0 @@
1
-
2
- # stub solr with "dara" array for testing.
3
- # if there is no data then it will behave like default blank search
4
- # this stub uses Kaminari for pagination
5
- # once the data is given, solr search block will call search
6
- # and that will create a new Search with arguments.
7
- # With the search, it passes the given data as @operation_context.
8
- # And "run_proc_block" method will go through each search conditions
9
- # in the block. while going through the block, it will change @operation_context
10
- #
11
- class Sunspot::Rails::StubSessionProxy
12
- attr_reader :block, :data, :range_fields
13
-
14
- def initialize(original_session, data = [])
15
- @original_session = original_session
16
- @data = data
17
- @range_fields = %w[time month created_at start end]
18
- end
19
-
20
- def set_range_fields(arr)
21
- @range_fields = arr
22
- end
23
-
24
- def search(*types, &block)
25
- Search.new(@data, types, &block)
26
- end
27
-
28
- # with and fulltext can be called multiple times
29
- # based on the block. that's why i am mutating @results
30
- #
31
- # for all and any block, we need to consult sunspot/lib/sunspot/dsl/standard_query.rb
32
- #
33
- class Search
34
- attr_reader :block, :types, :page, :per_page, :grouped_results
35
- def initialize(results = [], types = [], &block)
36
- @facets = {}
37
- @search_data = results
38
- @block = block
39
- @types = types
40
- @page = 1
41
- @per_page = 30
42
- @grouped_results = {}
43
- @group_key = ""
44
- @range_search_field = nil
45
- @operation_context = [{operation: "all", result: @search_data}]
46
- @current_context_index = 0
47
- @order_key = nil
48
- @order_direction = nil
49
- @textsearch_priorities = {}
50
- @search_term = ""
51
- run_proc_block
52
- end
53
-
54
- def final_results
55
- @operation_context.last[:result]
56
- end
57
-
58
- def results
59
- return PaginatedCollection.new if sorted_temp_result.empty?
60
- return sorted_temp_result if final_results.is_a? Kaminari::PaginatableArray
61
- Kaminari.paginate_array(sorted_temp_result).page(@page).per(@per_page)
62
- end
63
-
64
- def any_of(&bl)
65
- previous_result = result_for_current_context
66
- @operation_context.push({operation: "any", result: previous_result, temp_result: []})
67
- @current_context_index += 1
68
-
69
- Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
70
- after_results = @operation_context[@current_context_index][:temp_result]
71
-
72
- parent_temp_result = @operation_context[@current_context_index - 1][:temp_result]
73
-
74
-
75
- unless @current_context_index == 0
76
- @current_context_index -= 1
77
- @operation_context.pop
78
- end
79
- # parent is now current context
80
- if parent_temp_result
81
- @operation_context[@current_context_index][:temp_result] = parent_temp_result.concat(after_results)
82
- else
83
- @operation_context[@current_context_index][:result] = after_results
84
- end
85
- self
86
- end
87
-
88
- def all_of(&bl)
89
- previous_result = result_for_current_context
90
- @operation_context.push({operation: "all", result: previous_result})
91
- @current_context_index += 1
92
-
93
- Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl)
94
- after_results = result_for_current_context
95
- parent_temp_result = @operation_context[@current_context_index - 1][:temp_result]
96
-
97
- unless @current_context_index == 0
98
- @current_context_index -= 1
99
- @operation_context.pop
100
- end
101
-
102
- # parent is now current context
103
- if parent_temp_result
104
- @operation_context[@current_context_index][:temp_result] = parent_temp_result.concat(after_results)
105
- else
106
- @operation_context[@current_context_index][:result] = (previous_result & after_results).uniq
107
- end
108
-
109
- self
110
- end
111
-
112
- def with(attribute, value = "no_value")
113
- skip_no_value(attribute, value)
114
-
115
- if attribute == :search_class
116
- matches = result_for_current_context.select {|x| x.class.name == value }
117
- operation_context_result(matches)
118
- return self
119
- end
120
-
121
- if value == nil && attribute_is_about_range?(attribute)
122
- matches = result_for_current_context.select {|x| x.has_attribute?(attribute) && (x.read_attribute(attribute) == value)}
123
- operation_context_result(matches)
124
- return self
125
- end
126
-
127
- return self if value == "no_value" || attribute == :location
128
-
129
- matches = if value.class.name == "Array"
130
- result_for_current_context.select {|x| value.include?(x.send(attribute)) }
131
- elsif @types.map{|x| x.has_attribute?(attribute)}.include?(true)
132
- result_for_current_context.select {|x| x.read_attribute(attribute) == value }
133
- else
134
- result_for_current_context.select {|x| x.send(attribute) == value }
135
- end
136
- operation_context_result(matches)
137
- self
138
- end
139
-
140
- def without(attribute, value = "no_value")
141
- skip_no_value(attribute, value)
142
- if value == nil && attribute_is_about_range?(attribute)
143
- matches = result_for_current_context.select {|x| x.has_attribute?(attribute) && !(x.read_attribute(attribute) == value)}
144
- operation_context_result(matches)
145
- return self
146
- end
147
-
148
- return self if value == "no_value" || attribute == :location
149
- matches = if value.class.name == "Array"
150
- result_for_current_context.select {|x| x.has_attribute?(attribute) && !value.include?(x.send(attribute)) }
151
- else
152
- result_for_current_context.select {|x| x.has_attribute?(attribute) && !x.read_attribute(attribute).nil? && (x.read_attribute(attribute) != value) }
153
- end
154
- operation_context_result(matches)
155
- self
156
- end
157
-
158
- def hits
159
- self
160
- end
161
-
162
- # Added to pass .group_by
163
- # Idea is save grouped hash result seperately and then
164
- # access it with [] method.
165
- #
166
- # initial_grouped_hits = initial.hits.group_by(&:class_name)
167
- # initial_grouped_hits["Activity"]
168
- def [](key)
169
- return if @grouped_results.empty? || @grouped_results[key].nil?
170
- @group_key = key
171
- self
172
- end
173
-
174
- # possible situation
175
- #
176
- # initial_grouped_hits = initial.hits.group_by(&:class_name)
177
- # initial_grouped_hits["Activity"].map(&:primary_key)
178
- def map(&pr)
179
- return unless @grouped_results[@group_key]
180
- @grouped_results[@group_key].map{|x| x.id}
181
- end
182
-
183
- # possible situation
184
- #
185
- # initial_grouped_hits = initial.hits.group_by(&:class_name)
186
- def group_by(class_name = nil)
187
- return self if final_results.empty?
188
- @types.each do |type|
189
- @grouped_results[type.name] = sorted_temp_result.group_by{|i| i.class.name == type.name }[true]
190
- end
191
- self
192
- end
193
-
194
- def phrase_fields(arg)
195
- @textsearch_priorities = arg
196
- return self if @search_term.empty?
197
- fulltext(@search_term)
198
- end
199
-
200
- def query_phrase_slop(*args)
201
- fulltext(@search_term)
202
- end
203
-
204
- # with default `StubSessionProxy`, the results is blank whatever
205
- # argument or options you put in the search block
206
- def fulltext(term, opt={}, &bl)
207
- matches = []
208
- @search_term = term
209
- Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &bl) if !bl.nil?
210
- # if there is phrase_fields option then we only search for the field
211
- # to mimic the priority search. #string_text_fields
212
- @types.each do |type|
213
- string_text_fields(type).each do |field|
214
- if type.has_attribute?(field.to_sym)
215
- matches << type.where(type.arel_table[field.to_sym].matches("%#{term}%")).to_a
216
- end
217
- end
218
- end
219
- calculated = (result_for_current_context & matches.flatten.uniq)
220
- operation_context_result(calculated)
221
- self
222
- end
223
-
224
- def order_by(attribute, direction)
225
- @order_key = attribute
226
- @order_direction = direction
227
- self
228
- end
229
-
230
- def in_radius(*args)
231
- calculated = result_for_current_context.select do |r|
232
- distance = r&.location&.distance_from([args[0], args[1]])
233
- distance && distance < args[2].to_i
234
- end
235
- operation_context_result(calculated)
236
- self
237
- end
238
-
239
- def greater_than(time)
240
- calculated = result_for_current_context.select do |x|
241
- has_attribute_and_not_nil(x) &&
242
- x.read_attribute(@range_search_field) > time
243
- end.uniq
244
- operation_context_result(calculated)
245
- self
246
- end
247
-
248
- def greater_than_or_equal_to(time)
249
- calculated = result_for_current_context.select do |x|
250
- has_attribute_and_not_nil(x) &&
251
- x.read_attribute(@range_search_field) >= time
252
- end.uniq
253
- operation_context_result(calculated)
254
- self
255
- end
256
-
257
- def less_than(time)
258
- calculated = result_for_current_context.select do |x|
259
- has_attribute_and_not_nil(x) &&
260
- x.read_attribute(@range_search_field) < time
261
- end.uniq
262
- operation_context_result(calculated)
263
- self
264
- end
265
-
266
- def less_than_or_equal_to(time)
267
- calculated = result_for_current_context.select do |x|
268
- has_attribute_and_not_nil(x) &&
269
- x.read_attribute(@range_search_field) <= time
270
- end.uniq
271
- operation_context_result(calculated)
272
- self
273
- end
274
-
275
- def paginate(args={})
276
- @page = args[:page]
277
- @per_page = args[:per_page]
278
- self
279
- end
280
-
281
- def total
282
- @operation_context.last[:result].size
283
- end
284
-
285
- private
286
-
287
- # iterate the given block and change @operation_context
288
- # block may cotain with, without, fulltext, paginate, in_raius, order_by
289
- def run_proc_block
290
- result = @operation_context.last[:result]
291
- return PaginatedCollection.new if result.empty?
292
-
293
- Sunspot::Util::ContextBoundDelegate.instance_eval_with_context(self, &@block)
294
- # @operation_context.last[:result] = @operation_context.last[:result].uniq
295
- self
296
- end
297
-
298
- def current_operation
299
- @operation_context[@current_context_index][:operation]
300
- end
301
-
302
- def result_for_current_context
303
- @operation_context[@current_context_index][:result]
304
- end
305
-
306
- def temp_result_for_current_context
307
- @operation_context[@current_context_index][:temp_result]
308
- end
309
-
310
- def operation_context_result(after_results)
311
- previous_result = result_for_current_context
312
- # parent_result = @operation_context[@current_context_index -1][:result]
313
- temp_result = @operation_context[@current_context_index][:temp_result]
314
- if current_operation == "any"
315
- @operation_context[@current_context_index][:result] = previous_result.concat(after_results).uniq
316
- if temp_result
317
- @operation_context[@current_context_index][:temp_result] = temp_result.concat(after_results)
318
- end
319
- else
320
- @operation_context[@current_context_index][:result] = (previous_result & after_results).uniq
321
- end
322
- end
323
-
324
- def skip_no_value(attribute, value)
325
- if value == "no_value" && attribute_is_about_range?(attribute)
326
- matches = result_for_current_context.select {|x| x.has_attribute?(attribute)}
327
- operation_context_result(matches)
328
- return self
329
- end
330
- end
331
-
332
- def has_attribute_and_not_nil(x)
333
- x.read_attribute(@range_search_field) &&
334
- !x.read_attribute(@range_search_field).nil?
335
- end
336
-
337
- def string_text_fields(type)
338
- return @textsearch_priorities.keys unless @textsearch_priorities.keys.empty?
339
- type.columns.collect { |c| {"#{c.type}":"#{c.name}"} }
340
- .map{|hash| hash.select{|k,v| [:string, :text].include? k} }.uniq
341
- .map{|x| x.values}.flatten
342
- end
343
-
344
- def attribute_is_about_range?(attribute)
345
- matched = nil
346
- Sunspot.session.range_fields.each do |x|
347
- if attribute.to_s.include? x
348
- matched = true
349
- @range_search_field = attribute
350
- break
351
- end
352
- end
353
- matched
354
- end
355
-
356
- def sorted_temp_result
357
- return final_results unless @order_direction
358
- asc = final_results.sort_by{|x| x[@order_key]}
359
- return asc if @order_direction.to_s.downcase.include?("asc")
360
- asc.reverse
361
- end
362
- end
363
- end