spinel 0.2.0 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bfc0aae3285cb2efbb58eea915cde7314614c945
4
- data.tar.gz: fa7fb254ea04ddf0c93ff28f6a5fc10f637d40f3
3
+ metadata.gz: 15242d0a0aa570e1cdb2833f088015b8a123f728
4
+ data.tar.gz: 065227c4062d41de4e360ce359ad6d18a2119b41
5
5
  SHA512:
6
- metadata.gz: 744409a98574cbf84ea2e22e7442296c7322a1ced06047a4b6346df7a9b1d8db95fa340553beb501ee3812b83f53de28935278daa0e7f112a58d377cc9f9ee83
7
- data.tar.gz: db068029c776eb8d28e5dcc1d45fe58e09ca1fe95f9c99a26bc6063cbb58eba5d96f2524451a898b19f2b3559471b1ff4cce18b785e5061f1d633010c11ffbda
6
+ metadata.gz: 0cedf931a094181409bf4bda8fd05267b3da5aefa860f3d3c78fda7ba61575cebaacc871bab8cc8247991a00e85884887374eff45b2bdfbe2fbe805239573c98
7
+ data.tar.gz: a7e79c12d900d2f6030c6d95366aea711b1dea944d8d5649ecc583e76443e174e2de1d5e853ba5e3f1fb5a0270ab3aa7da39ed3c26c9fa5cadea12d2d03b4fc5
data/.gitignore CHANGED
@@ -13,5 +13,5 @@
13
13
  *.o
14
14
  *.a
15
15
  mkmf.log
16
-
17
16
  .DS_Store
17
+ .env
@@ -1,3 +1,7 @@
1
+ # 0.3.0
2
+
3
+ * オプション名称を変更
4
+
1
5
  # 0.2.0
2
6
 
3
7
  * 空白文字列の取り扱いをPOSIX文字クラスで指定
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Spinel
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/spinel.svg)](http://badge.fury.io/rb/spinel)
4
+ [![Code Climate](https://codeclimate.com/github/k-shogo/spinel/badges/gpa.svg)](https://codeclimate.com/github/k-shogo/spinel)
5
+ [![Test Coverage](https://codeclimate.com/github/k-shogo/spinel/badges/coverage.svg)](https://codeclimate.com/github/k-shogo/spinel)
6
+
3
7
  Spinel is Redis based lightweight text search engine.
4
8
  SpinelはRedisをバックエンドに用いた軽量なテキスト検索システムです。
5
9
 
@@ -72,9 +76,9 @@ spinel.search 'usu'
72
76
  ```ruby
73
77
  Spinel.configure do |config|
74
78
  config.redis = 'redis://127.0.0.1:6379/0'
75
- config.min_complete = 2
79
+ config.minimal_word = 2
76
80
  config.cache_expire = 600
77
- config.match_limit = 10
81
+ config.search_limit = 10
78
82
  config.document_key = :body
79
83
  config.namespace = 'spinel'
80
84
  end
data/Rakefile CHANGED
@@ -1,2 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
2
3
 
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.pattern = 'test/*_test.rb'
7
+ end
@@ -1,23 +1,23 @@
1
1
  module Spinel
2
2
  module Config
3
- DEFAULT_MIN_COMPLETE = 2
3
+ DEFAULT_MINIMAL_WORD = 2
4
4
  DEFAULT_CACHE_EXPIRE = 600
5
- DEFAULT_MATCH_LIMIT = 10
5
+ DEFAULT_SEARCH_LIMIT = 10
6
6
  DEFAULT_DOCUMENT_KEY = :body
7
7
  DEFAULT_NAMESPACE = 'spinel'
8
8
 
9
- attr_writer :min_complete, :cache_expire, :match_limit, :document_key, :namespace
9
+ attr_writer :minimal_word, :cache_expire, :search_limit, :document_key, :namespace
10
10
 
11
- def min_complete
12
- @min_complete ||= DEFAULT_MIN_COMPLETE
11
+ def minimal_word
12
+ @minimal_word ||= DEFAULT_MINIMAL_WORD
13
13
  end
14
14
 
15
15
  def cache_expire
16
16
  @cache_expire ||= DEFAULT_CACHE_EXPIRE
17
17
  end
18
18
 
19
- def match_limit
20
- @match_limit ||= DEFAULT_MATCH_LIMIT
19
+ def search_limit
20
+ @search_limit ||= DEFAULT_SEARCH_LIMIT
21
21
  end
22
22
 
23
23
  def document_key
@@ -35,7 +35,6 @@ module Spinel
35
35
  else
36
36
  @redis = server
37
37
  end
38
-
39
38
  redis
40
39
  end
41
40
 
@@ -1,9 +1,9 @@
1
1
  module Spinel
2
2
  module Helper
3
3
 
4
- def prefixes_for_phrase(phrase)
5
- squish(phrase).split.flat_map do |w|
6
- (Spinel.min_complete-1..(w.length-1)).map{ |l| w[0..l] }
4
+ def prefixes str
5
+ squish(str).split.flat_map do |w|
6
+ (Spinel.minimal_word-1..(w.length-1)).map{ |l| w[0..l] }
7
7
  end.uniq
8
8
  end
9
9
 
@@ -11,8 +11,12 @@ module Spinel
11
11
  str.to_s.gsub(/[[:space:]]+/, ' ').strip
12
12
  end
13
13
 
14
- def document_validate doc
15
- raise ArgumentError, "documents must specify both an id and a body" unless document_id(doc) && document_body(doc)
14
+ def get_valid_document doc
15
+ id = document_id doc
16
+ body = document_body doc
17
+ score = document_score doc
18
+ raise ArgumentError, "documents must specify both an id and a body" unless id && body
19
+ [id, body, score]
16
20
  end
17
21
 
18
22
  def document_id doc
@@ -20,7 +24,7 @@ module Spinel
20
24
  end
21
25
 
22
26
  def document_score doc
23
- doc[:score] || doc["score"]
27
+ (doc[:score] || doc["score"]).to_f
24
28
  end
25
29
 
26
30
  def document_body doc
@@ -3,17 +3,17 @@ module Spinel
3
3
 
4
4
  def store doc, opts = {}
5
5
  opts = { skip_duplicate_check: false }.merge(opts)
6
- document_validate doc
7
- id = document_id doc
6
+ id, body, score = get_valid_document doc
8
7
 
9
8
  remove(id: id) unless opts[:skip_duplicate_check]
10
9
 
11
10
  Spinel.redis.pipelined do
12
11
  Spinel.redis.hset(database, id, MultiJson.encode(doc))
13
- prefixes_for_phrase(document_body(doc)).each do |p|
14
- Spinel.redis.zadd(index(p), document_score(doc), id)
12
+ prefixes(body).each do |p|
13
+ Spinel.redis.zadd(index(p), score, id)
15
14
  end
16
15
  end
16
+ doc
17
17
  end
18
18
 
19
19
  def get id
@@ -28,7 +28,7 @@ module Spinel
28
28
  prev_id = document_id prev_doc
29
29
  Spinel.redis.pipelined do
30
30
  Spinel.redis.hdel(database, prev_id)
31
- prefixes_for_phrase(document_body(prev_doc)).each do |p|
31
+ prefixes(document_body(prev_doc)).each do |p|
32
32
  Spinel.redis.zrem(index(p), prev_id)
33
33
  end
34
34
  end
@@ -2,22 +2,34 @@ module Spinel
2
2
  module Searcher
3
3
 
4
4
  def search term, options = {}
5
- options = { limit: Spinel.match_limit, cache: true }.merge(options)
6
-
7
- words = squish(term).split.reject{|w| w.size < Spinel.min_complete}.sort
5
+ options = search_option options
6
+ words = search_word_split term
8
7
  return [] if words.empty?
9
8
 
10
9
  tmp_cachekey = cachekey(words)
11
10
 
12
- unless options[:cache] && Spinel.redis.exists(tmp_cachekey)
13
- interkeys = words.map{ |w| index w }
14
- Spinel.redis.zinterstore(tmp_cachekey, interkeys)
15
- Spinel.redis.expire(tmp_cachekey, Spinel.cache_expire)
16
- end
11
+ set_cache(tmp_cachekey, words) unless options[:cache] && cache_enable(tmp_cachekey)
17
12
 
18
13
  ids = Spinel.redis.zrevrange(tmp_cachekey, 0, options[:limit] - 1)
19
14
  ids.empty? ? [] : Spinel.redis.hmget(database, *ids).compact.map{|json| MultiJson.decode(json)}
15
+ end
16
+
17
+ def search_option options = {}
18
+ { limit: Spinel.minimal_word, cache: true }.merge(options)
19
+ end
20
+
21
+ def search_word_split word
22
+ squish(word).split.reject{|w| w.size < Spinel.minimal_word}.sort
23
+ end
24
+
25
+ def set_cache key, words
26
+ interkeys = words.map{ |w| index w }
27
+ Spinel.redis.zinterstore(key, interkeys)
28
+ Spinel.redis.expire(key, Spinel.cache_expire)
29
+ end
20
30
 
31
+ def cache_enable key
32
+ Spinel.redis.exists(key)
21
33
  end
22
34
 
23
35
  end
@@ -1,6 +1,6 @@
1
1
  module Spinel
2
2
  MAJOR = 0
3
- MINOR = 2
3
+ MINOR = 3
4
4
  PATCH = 0
5
5
  VERSION = [MAJOR, MINOR, PATCH].compact.join('.')
6
6
  end
@@ -23,4 +23,10 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_development_dependency "bundler", "~> 1.7"
25
25
  spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", ">= 5.4"
27
+ spec.add_development_dependency "minitest-power_assert"
28
+ spec.add_development_dependency "simplecov"
29
+ spec.add_development_dependency "codeclimate-test-reporter"
30
+ spec.add_development_dependency "dotenv"
31
+ spec.add_development_dependency "mock_redis"
26
32
  end
@@ -0,0 +1,120 @@
1
+ require 'test_helper'
2
+
3
+ class SpinelTest < Minitest::Test
4
+ def setup
5
+ @spinel = Spinel.new
6
+ end
7
+
8
+ def test_redis
9
+ assert { 'PONG' == Spinel.redis.ping }
10
+
11
+ Spinel.redis = 'redis://127.0.0.1:6379/0'
12
+ assert { 'PONG' == Spinel.redis.ping }
13
+
14
+ mock_redis = MockRedis.new
15
+ Spinel.redis = mock_redis
16
+ assert { mock_redis.object_id == Spinel.redis.object_id }
17
+ end
18
+
19
+ def test_respond_to?
20
+ assert Spinel.respond_to? :store
21
+ assert Spinel.respond_to? :remove
22
+ assert Spinel.respond_to? :get
23
+ assert Spinel.respond_to? :search
24
+ end
25
+
26
+ def test_method_missing
27
+ Spinel.redis.flushall
28
+ Spinel.store({id: 1, body: 'test'},{})
29
+ assert { 1 == Spinel.redis.hlen(@spinel.database) }
30
+ end
31
+
32
+ def test_database_index
33
+ assert { 'spinel:data:default' == @spinel.database }
34
+ end
35
+
36
+ def test_index
37
+ assert { 'spinel:index:default:term' == @spinel.index(:term) }
38
+ end
39
+
40
+ def test_cachekey_index
41
+ assert { 'spinel:cache:default:word1|word2' == @spinel.cachekey(%w(word1 word2)) }
42
+ end
43
+
44
+ def test_prefixes
45
+ assert { ["ta", "tak", "take", "tal", "talk"] == @spinel.prefixes("take talk") }
46
+ end
47
+
48
+ def test_squish
49
+ assert { "quick brown fox" == @spinel.squish(" quick \t brown\n fox ") }
50
+ end
51
+
52
+ def test_get_valid_document
53
+ assert_raises(ArgumentError){ @spinel.get_valid_document({id: 1}) }
54
+ assert_raises(ArgumentError){ @spinel.get_valid_document({body: 'body'}) }
55
+ assert { [1, 'body', 0.0] == @spinel.get_valid_document({id: 1, body: 'body'}) }
56
+ end
57
+
58
+ def test_document_id
59
+ assert { "doc_id" == @spinel.document_id({id: "doc_id"}) }
60
+ assert { "doc_id" == @spinel.document_id({"id" => "doc_id"}) }
61
+ end
62
+
63
+ def test_document_score
64
+ assert { 1.0 == @spinel.document_score({score: 1}) }
65
+ assert { 1.0 == @spinel.document_score({"score" => 1}) }
66
+ assert { 0.0 == @spinel.document_score({}) }
67
+ end
68
+
69
+ def test_document_body
70
+ assert { "doc_body" == @spinel.document_body({body: "doc_body"}) }
71
+ assert { "doc_body" == @spinel.document_body({"body" => "doc_body"}) }
72
+ end
73
+
74
+ def test_minimal_word
75
+ assert { 2 == Spinel.minimal_word }
76
+ end
77
+
78
+ def test_cache_expire
79
+ assert { 600 == Spinel.cache_expire }
80
+ end
81
+
82
+ def test_search_limit
83
+ assert { 10 == Spinel.search_limit }
84
+ end
85
+
86
+ def test_document_key
87
+ assert { 'body' == Spinel.document_key }
88
+ end
89
+
90
+ def test_namespace
91
+ assert { 'spinel' == Spinel.namespace }
92
+ end
93
+
94
+ def test_store
95
+ Spinel.redis.flushall
96
+ @spinel.store id: 1, body: 'test'
97
+ assert { 1 == Spinel.redis.hlen(@spinel.database) }
98
+ end
99
+
100
+ def test_get
101
+ Spinel.redis.flushall
102
+ @spinel.store id: 1, body: 'test'
103
+ assert { {"id" => 1, "body" => 'test'} == @spinel.get(1) }
104
+ end
105
+
106
+ def test_remove
107
+ Spinel.redis.flushall
108
+ @spinel.store id: 1, body: 'test'
109
+ @spinel.remove id: 1
110
+ assert { 0 == Spinel.redis.hlen(@spinel.database) }
111
+ end
112
+
113
+ def test_search
114
+ Spinel.redis.flushall
115
+ @spinel.store id: 1, body: 'ruby'
116
+ @spinel.store id: 2, body: 'rubx'
117
+ assert { 2 == @spinel.search('rub').size }
118
+ assert { 1 == @spinel.search('ruby').size }
119
+ end
120
+ end
@@ -0,0 +1,39 @@
1
+ require 'dotenv'
2
+ require "codeclimate-test-reporter"
3
+ require 'simplecov'
4
+
5
+ Dotenv.load
6
+ CodeClimate::TestReporter.start if ENV['CCR']
7
+
8
+ SimpleCov.start do
9
+ add_filter "vendor"
10
+ end
11
+
12
+ require 'spinel'
13
+ require 'mock_redis'
14
+ require 'minitest/autorun'
15
+ require 'minitest/unit'
16
+ require 'minitest-power_assert'
17
+
18
+ Spinel.configure do |config|
19
+ config.redis = MockRedis.new
20
+ config.minimal_word = 2
21
+ config.cache_expire = 600
22
+ config.search_limit = 10
23
+ config.document_key = :body
24
+ config.namespace = 'spinel'
25
+ end
26
+
27
+ def test_data
28
+ [
29
+ {id: 1, body: 'and all with pearl and ruby glowing'},
30
+ {id: 2, body: 'yellow or orange variety of ruby spinel'},
31
+ {id: 3, body: 'colour called pearl yellow'},
32
+ {id: 4, body: 'mandarin orange net sack'},
33
+ {id: 5, body: 'spinel used as a gemstone usually dark red'},
34
+ {id: 6, body: 'today is hotter than usual'},
35
+ {id: 7, body: 'call on a person'},
36
+ {id: 8, body: 'that gem is shining'},
37
+ {id: 9, body: 'polish shoes to a bright shine'}
38
+ ]
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spinel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - k-shogo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-03 00:00:00.000000000 Z
11
+ date: 2015-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -66,6 +66,90 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '5.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '5.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-power_assert
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codeclimate-test-reporter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: dotenv
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: mock_redis
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
69
153
  description: lightweight text search system on Redis
70
154
  email:
71
155
  - platycod0n.ramosa@gmail.com
@@ -87,6 +171,8 @@ files:
87
171
  - lib/spinel/searcher.rb
88
172
  - lib/spinel/version.rb
89
173
  - spinel.gemspec
174
+ - test/spinel_test.rb
175
+ - test/test_helper.rb
90
176
  homepage: ''
91
177
  licenses:
92
178
  - MIT
@@ -111,4 +197,6 @@ rubygems_version: 2.2.2
111
197
  signing_key:
112
198
  specification_version: 4
113
199
  summary: lightweight text search system on Redis
114
- test_files: []
200
+ test_files:
201
+ - test/spinel_test.rb
202
+ - test/test_helper.rb