picky 4.3.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,8 @@
1
1
  module Picky
2
2
 
3
3
  class Category
4
+
5
+ class Picky::IdNotGivenException < StandardError; end
4
6
 
5
7
  # Removes an indexed object with the
6
8
  # given id.
@@ -31,11 +33,9 @@ module Picky
31
33
  # Note: Takes a hash as opposed to the add/replace method.
32
34
  #
33
35
  def replace_from hash
34
- # TODO Decide on a format.
35
- #
36
36
  return unless text = hash[from] || hash[from.to_s]
37
37
 
38
- id = hash[:id] || hash['id']
38
+ raise IdNotGivenException.new unless id = hash[:id] || hash['id']
39
39
  id = id.send key_format
40
40
 
41
41
  remove id
@@ -4,18 +4,44 @@ module Picky
4
4
  module IndexActions
5
5
 
6
6
  def self.extended base
7
+ # Updates the given item and returns HTTP codes:
8
+ # * 200 if the index has been updated or no error case has occurred.
9
+ # * 404 if the index cannot be found.
10
+ # * 400 if no data or item id has been provided in the data.
11
+ #
12
+ # Note: 200 returns no data yet.
13
+ #
7
14
  base.post '/' do
8
15
  index_name = params['index']
9
- index = Picky::Indexes[index_name.to_sym]
10
- data = params['data']
11
- index.replace_from Yajl::Parser.parse(data) if data
16
+ begin
17
+ index = Picky::Indexes[index_name.to_sym]
18
+ data = params['data']
19
+ return 400 unless data
20
+ data && index.replace_from(Yajl::Parser.parse data) && 200
21
+ rescue IdNotGivenException
22
+ 400
23
+ rescue StandardError
24
+ 404
25
+ end
12
26
  end
27
+
28
+ # Deletes the given item and returns:
29
+ # * 200 if the index has been updated or no error case has occurred.
30
+ # * 404 if the index cannot be found.
31
+ # * 400 if no data or item id has been provided in the data.
32
+ #
33
+ # Note: 200 returns no data yet.
34
+ #
13
35
  base.delete '/' do
14
36
  index_name = params['index']
15
- index = Picky::Indexes[index_name.to_sym]
16
- data = Yajl::Parser.parse params['data']
17
- id = data['id']
18
- index.remove id if id
37
+ begin
38
+ index = Picky::Indexes[index_name.to_sym]
39
+ data = Yajl::Parser.parse params['data']
40
+ id = data['id']
41
+ id ? index.remove(id) && 200 : 400
42
+ rescue StandardError
43
+ 404
44
+ end
19
45
  end
20
46
  end
21
47
 
@@ -29,5 +29,16 @@ describe Picky::Category, "Realtime API" do
29
29
  it 'offers an unshift method' do
30
30
  category.unshift Thing.new(1, 'text')
31
31
  end
32
+ it 'offers a replace_from method' do
33
+ category.replace_from id: 1, text: "some text"
34
+ end
35
+ it 'raises on no id given' do
36
+ expect {
37
+ category.replace_from text: "some text"
38
+ }.to raise_error
39
+ end
40
+ it 'shrugs off no data given' do
41
+ category.replace_from id: 1
42
+ end
32
43
 
33
44
  end
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'spec_helper'
4
+
5
+ describe Picky::Tokenizer do
6
+ describe 'examples' do
7
+ it 'works correctly' do
8
+ tokenizer = described_class.new(split_words_on: /\&/, normalizes_words: [[/\&/, 'and']])
9
+
10
+ # TODO Is this really correct? Shouldn't we split after normalizing?
11
+ #
12
+ tokenizer.tokenize('M & M').should == [['m', 'and', 'm'], ['m', 'and', 'm']]
13
+ end
14
+ it 'works correctly' do
15
+ tokenizer = described_class.new(stopwords: /\b(and)\b/, normalizes_words: [[/\&/, 'and']])
16
+
17
+ # TODO Is this really correct? Shouldn't we stop words after normalizing?
18
+ #
19
+ tokenizer.tokenize('M & M').should == [['m', 'and', 'm'], ['m', 'and', 'm']]
20
+ end
21
+ it 'removes all stopwords if they do not occur alone' do
22
+ tokenizer = described_class.new(stopwords: /\b(and|then)\b/)
23
+ tokenizer.tokenize('and then').should == [[], []]
24
+ end
25
+ it 'does not remove a stopword if it occurs alone' do
26
+ tokenizer = described_class.new(stopwords: /\b(and|then)\b/)
27
+ tokenizer.tokenize('and').should == [['and'], ['and']]
28
+ end
29
+ it 'does not remove a stopword if it occurs alone (even with qualifier)' do
30
+ tokenizer = described_class.new(stopwords: /\b(and|then)\b/)
31
+ tokenizer.tokenize('title:and').should == [['title:and'], ['title:and']]
32
+ end
33
+ it 'does not remove a stopword if it occurs alone (even with qualifier)' do
34
+ tokenizer = described_class.new(stopwords: /\b(and|then)\b/)
35
+ tokenizer.tokenize('title:and').should == [['title:and'], ['title:and']]
36
+ end
37
+ it 'removes stopwords, then only lets through max words words' do
38
+ tokenizer = described_class.new(stopwords: /\b(and|then|to|the)\b/, max_words: 2)
39
+ tokenizer.tokenize('and then they went to the house').should == [['they', 'went'], ['they', 'went']]
40
+ end
41
+ it 'can take freaky splits_text_on options' do
42
+ tokenizer = described_class.new(splits_text_on: /([A-Z]?[a-z]+)/, case_sensitive: false) # Explicit case, is false by default.
43
+ tokenizer.tokenize('TOTALCamelCaseExample').should == [
44
+ ["total", "camel", "case", "example"],
45
+ ["total", "camel", "case", "example"]
46
+ ]
47
+ end
48
+ it 'substitutes and removes in the right order' do
49
+ tokenizer = described_class.new(
50
+ substitutes_characters_with: Picky::CharacterSubstituters::WestEuropean.new,
51
+ removes_characters: /e/
52
+ )
53
+
54
+ # Ä -> Ae -> A
55
+ #
56
+ tokenizer.tokenize('Ä ä').should == [['a', 'a'], ['a', 'a']]
57
+ end
58
+ it 'removes characters, then only lets through an ok sized token' do
59
+ tokenizer = described_class.new(rejects_token_if: ->(token){ token.size >= 5 }, removes_characters: /e/)
60
+ tokenizer.tokenize('hullo').should == [[], []]
61
+ tokenizer.tokenize('hello').should == [['hllo'], ['hllo']]
62
+ end
63
+ it 'is case sensitive' do
64
+ tokenizer = described_class.new(case_sensitive: true)
65
+ tokenizer.tokenize('Kaspar codes').should == [['Kaspar', 'codes'], ['Kaspar', 'codes']]
66
+ end
67
+ it 'is case sensitive, also for removing characters' do
68
+ tokenizer = described_class.new(case_sensitive: true, removes_characters: /K/)
69
+ tokenizer.tokenize('Kaspar codes').should == [['aspar', 'codes'], ['aspar', 'codes']]
70
+ end
71
+ end
72
+ end
@@ -1,61 +1,196 @@
1
1
  require 'yajl'
2
2
  require 'sinatra'
3
- require 'picky-client'
3
+ require_relative '../../../client/lib/picky-client'
4
+ require_relative '../../../client/lib/picky-client/spec'
4
5
  require 'spec_helper'
5
6
 
6
7
  describe 'Sinatra Index Actions' do
7
8
 
8
- # This is the application that is tested.
9
- #
10
- class MyPickyServer < Sinatra::Base
11
- extend Picky::Sinatra::IndexActions
9
+ before(:all) do
12
10
 
13
- data = Picky::Index.new :index do
14
- category :name
15
- category :surname
16
- end
11
+ # This is the application that is tested.
12
+ #
13
+ class MyIndexActionsPickyServer < Sinatra::Base
14
+ extend Picky::Sinatra::IndexActions
15
+
16
+ data = Picky::Index.new :some_index do
17
+ category :name
18
+ category :surname
19
+ end
17
20
 
18
- people = Picky::Search.new data
21
+ people = Picky::Search.new data
19
22
 
20
- get '/people' do
21
- results = people.search params[:query], params[:ids] || 20, params[:offset] || 0
22
- results.to_json
23
+ get '/people' do
24
+ results = people.search params[:query], params[:ids] || 20, params[:offset] || 0
25
+ results.to_json
26
+ end
23
27
  end
28
+
24
29
  end
25
30
 
26
31
  describe 'updating' do
27
32
  before(:each) do
28
33
  Picky::Indexes.clear
29
34
  end
30
- let(:request) { ::Rack::MockRequest.new MyPickyServer }
31
- it 'should update the index correctly' do
32
- request.post('/', params: { index: 'index', data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }} })
35
+ let(:request) { ::Rack::MockRequest.new MyIndexActionsPickyServer }
36
+ context 'return values' do
37
+ describe 'update' do
38
+ it 'returns a correct code after updating without problems' do
39
+ result = request.post('/', params: {
40
+ index: 'some_index',
41
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
42
+ })
43
+ result.status.should == 200
44
+ end
45
+ it 'returns a correct code after updating with just the id' do
46
+ result = request.post('/', params: {
47
+ index: 'some_index',
48
+ data: %Q{{ "id":"1" }}
49
+ })
50
+ result.status.should == 200
51
+ end
52
+ it 'returns a correct code after updating without id' do
53
+ result = request.post('/', params: {
54
+ index: 'some_index',
55
+ data: %Q{{ "name":"Florian", "surname":"Hanke" }}
56
+ })
57
+ result.status.should == 400
58
+ end
59
+ it 'returns a correct code after updating with the wrong index' do
60
+ result = request.post('/', params: {
61
+ index: 'some_wrong_index',
62
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
63
+ })
64
+ result.status.should == 404
65
+ end
66
+ end
67
+ describe 'delete' do
68
+ before(:each) do
69
+ request.post('/', params: {
70
+ index: 'some_index',
71
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
72
+ })
73
+ end
74
+ it 'returns a correct code after deleting without problems' do
75
+ result = request.delete('/', params: {
76
+ index: 'some_index',
77
+ data: %Q{{ "id":"1" }}
78
+ })
79
+ result.status.should == 200
80
+ end
81
+ it 'returns a correct code after deleting twice' do
82
+ result = request.delete('/', params: {
83
+ index: 'some_index',
84
+ data: %Q{{ "id":"1" }}
85
+ })
86
+ result = request.delete('/', params: {
87
+ index: 'some_index',
88
+ data: %Q{{ "id":"1" }}
89
+ })
90
+ result.status.should == 200
91
+ end
92
+ it 'returns a correct code after deleting without id' do
93
+ result = request.delete('/', params: {
94
+ index: 'some_index',
95
+ data: %Q{{}}
96
+ })
97
+ result.status.should == 400
98
+ end
99
+ it 'returns a correct code after deleting with the wrong index' do
100
+ result = request.delete('/', params: {
101
+ index: 'some_wrong_index',
102
+ data: %Q{{ "id":"1" }}
103
+ })
104
+ result.status.should == 404
105
+ end
106
+ end
107
+ end
108
+ context '' do
109
+ it 'updates the index correctly' do
110
+ request.post('/', params: {
111
+ index: 'some_index',
112
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
113
+ })
33
114
 
34
- results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
35
- results['total'].should == 1
115
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
116
+ results['total'].should == 1
36
117
 
37
- request.post('/', params: { index: 'index', data: %Q{{ "id":"2", "name":"Florian", "surname":"Meier" }} })
118
+ request.post('/', params: {
119
+ index: 'some_index',
120
+ data: %Q{{ "id":"2", "name":"Florian", "surname":"Meier" }}
121
+ })
38
122
 
39
- results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
40
- results['total'].should == 2
41
- end
42
- it 'should delete entries from the index correctly' do
43
- request.post('/', params: { index: 'index', data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }} })
44
- request.post('/', params: { index: 'index', data: %Q{{ "id":"2", "name":"Florian", "surname":"Meier" }} })
123
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
124
+ results['total'].should == 2
125
+ end
126
+ it 'updates the index correctly' do
127
+ request.post('/', params: {
128
+ index: 'some_index',
129
+ data: %Q{{ "id":"1", "name":"Flarian", "surname":"Hanke" }}
130
+ })
45
131
 
46
- results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
47
- results['total'].should == 2
132
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'hanke' }).body
133
+ results['total'].should == 1
48
134
 
49
- request.delete('/', params: { index: 'index', data: %Q{{ "id":"1" }} })
135
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
136
+ results['total'].should == 0
50
137
 
51
- results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
52
- results['total'].should == 1
53
- end
54
- it 'should have no problem with a superfluous delete' do
55
- request.delete('/', params: { index: 'index', data: %Q{{ "id":"1" }} })
138
+ # Whoops, typo. Let's fix it.
139
+ #
140
+ request.post('/', params: {
141
+ index: 'some_index',
142
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
143
+ })
144
+
145
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'hanke' }).body
146
+ results['total'].should == 1
147
+
148
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'flarian' }).body
149
+ results['total'].should == 0
150
+
151
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
152
+ results['total'].should == 1
153
+ end
154
+ it 'deletes entries from the index correctly' do
155
+ request.post('/', params: {
156
+ index: 'some_index',
157
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
158
+ })
159
+ request.post('/', params: {
160
+ index: 'some_index',
161
+ data: %Q{{ "id":"2", "name":"Florian", "surname":"Meier" }}
162
+ })
163
+
164
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
165
+ results['total'].should == 2
166
+
167
+ request.delete('/', params: {
168
+ index: 'some_index',
169
+ data: %Q{{ "id":"1" }}
170
+ })
171
+
172
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
173
+ results['total'].should == 1
174
+ end
175
+ it 'has no problem with a superfluous delete' do
176
+ request.delete('/', params: {
177
+ index: 'some_index',
178
+ data: %Q{{ "id":"1" }}
179
+ })
180
+
181
+ results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
182
+ results['total'].should == 0
183
+ end
184
+ it 'works with the (test) client' do
185
+ client = Picky::TestClient.new MyIndexActionsPickyServer, :path => '/people'
186
+
187
+ request.post('/', params: {
188
+ index: 'some_index',
189
+ data: %Q{{ "id":"1", "name":"Florian", "surname":"Hanke" }}
190
+ })
56
191
 
57
- results = Yajl::Parser.parse request.get('/people', params: { query: 'florian' }).body
58
- results['total'].should == 0
192
+ client.search('florian').total.should == 1
193
+ end
59
194
  end
60
195
  end
61
196
 
@@ -96,7 +96,9 @@ ERROR
96
96
  before(:each) do
97
97
  tokenizer.normalizes_words([
98
98
  [/st\./, 'sankt'],
99
- [/stras?s?e?/, 'str']
99
+ [/stras?s?e?/, 'str'],
100
+ [/\+/, 'plus'],
101
+ [/\&/, 'and']
100
102
  ])
101
103
  end
102
104
  it "has normalize_with_patterns" do
@@ -105,6 +107,12 @@ ERROR
105
107
  it "normalizes, but just the first one" do
106
108
  tokenizer.normalize_with_patterns('st. wegstrasse').should == 'sankt wegstrasse'
107
109
  end
110
+ it "works correctly" do
111
+ tokenizer.normalize_with_patterns('camera +').should == 'camera plus'
112
+ end
113
+ it "works correctly" do
114
+ tokenizer.normalize_with_patterns('alice & bob').should == 'alice and bob'
115
+ end
108
116
  end
109
117
  end
110
118
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: picky
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 4.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-13 00:00:00.000000000 Z
12
+ date: 2012-03-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70126521728720 !ruby/object:Gem::Requirement
16
+ requirement: &70193563400740 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,21 +21,21 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70126521728720
24
+ version_requirements: *70193563400740
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: picky-client
27
- requirement: &70126521728220 !ruby/object:Gem::Requirement
27
+ requirement: &70193563400240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
31
31
  - !ruby/object:Gem::Version
32
- version: 4.3.0
32
+ version: 4.3.1
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70126521728220
35
+ version_requirements: *70193563400240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: text
38
- requirement: &70126521727800 !ruby/object:Gem::Requirement
38
+ requirement: &70193563399820 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70126521727800
46
+ version_requirements: *70193563399820
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: yajl-ruby
49
- requirement: &70126521727340 !ruby/object:Gem::Requirement
49
+ requirement: &70193563399360 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70126521727340
57
+ version_requirements: *70193563399360
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: activesupport
60
- requirement: &70126521726800 !ruby/object:Gem::Requirement
60
+ requirement: &70193563415200 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '3.0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70126521726800
68
+ version_requirements: *70193563415200
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: procrastinate
71
- requirement: &70126521726280 !ruby/object:Gem::Requirement
71
+ requirement: &70193563414580 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0.4'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *70126521726280
79
+ version_requirements: *70193563414580
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rack_fast_escape
82
- requirement: &70126521725880 !ruby/object:Gem::Requirement
82
+ requirement: &70193563414120 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *70126521725880
90
+ version_requirements: *70193563414120
91
91
  description: Fast Ruby semantic text search engine with comfortable single field interface.
92
92
  email: florian.hanke+picky@gmail.com
93
93
  executables:
@@ -259,6 +259,7 @@ files:
259
259
  - spec/functional/regression_spec.rb
260
260
  - spec/functional/speed_spec.rb
261
261
  - spec/functional/terminate_early_spec.rb
262
+ - spec/functional/tokenizer_spec.rb
262
263
  - spec/integration/sinatra_index_actions_spec.rb
263
264
  - spec/lib/analytics_spec.rb
264
265
  - spec/lib/analyzer_spec.rb
@@ -408,6 +409,7 @@ test_files:
408
409
  - spec/functional/regression_spec.rb
409
410
  - spec/functional/speed_spec.rb
410
411
  - spec/functional/terminate_early_spec.rb
412
+ - spec/functional/tokenizer_spec.rb
411
413
  - spec/integration/sinatra_index_actions_spec.rb
412
414
  - spec/lib/analytics_spec.rb
413
415
  - spec/lib/analyzer_spec.rb