gorse 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/gorse.rb +296 -16
  3. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de1508be3a15a7aefd31a677989145e16c1ecc19633abb8cc173f462a6ea924f
4
- data.tar.gz: 3bac22cda6b6684942fa60591e3c52fffcd6774ee7e22624ad094c324d4dee5d
3
+ metadata.gz: 842507a7200f7259e97c07f2a6e9f530013a5711a9572e426c21552786f18603
4
+ data.tar.gz: 2b62c853f688dd49687b8dfcd1bdc56a5a33cb29dc978c3e405b6919a3ab3e83
5
5
  SHA512:
6
- metadata.gz: 1369d41be05ce573fba6846d7c96b820c592fe41bc9ee3e0b4a557fbe1ddce6984eeeddb69fda494b9a6700e51afe64c861c73d41249db2c3e18e75cc0f5698c
7
- data.tar.gz: aadef649c5fa603a2614f18a152ed923de9361d0e73fbc521365e840014883b8ab7a2160e9d1a1f1ca627b8cbdb5841ad4ff4594862b5d8c6db4b59cf6c6b47a
6
+ metadata.gz: b6885854bd2236f9f099a9edafb2e6171c96b8766ec9228743fb697ef05436807df0fd86e3386a9bf50e356d2fb51049f4283b7b7450bbd906f0f307110512e0
7
+ data.tar.gz: 49f611ef4d496254942bf4f90bb01cdfdf402435e3748df08232f9b276f6faef4d342e8ff68f48e1b8d247eb4f092b684868757a9fdef49f1c097daa8085e643
data/lib/gorse.rb CHANGED
@@ -1,21 +1,26 @@
1
1
  require 'json'
2
2
  require 'net/http'
3
+ require 'uri'
4
+ require 'time'
3
5
 
4
6
  class Feedback
5
- def initialize(feedback_type, user_id, item_id, timestamp)
7
+ def initialize(feedback_type, user_id, item_id, value, timestamp)
6
8
  @feedback_type = feedback_type
7
9
  @user_id = user_id
8
10
  @item_id = item_id
11
+ @value = value
9
12
  @timestamp = timestamp
10
13
  end
11
14
 
12
15
  def to_json(options = {})
13
- {
16
+ data = {
14
17
  'FeedbackType' => @feedback_type,
15
18
  'UserId' => @user_id,
16
19
  'ItemId' => @item_id,
17
- 'Timestamp' => @timestamp
18
- }.to_json(options)
20
+ }
21
+ data['Value'] = @value unless @value.nil?
22
+ data['Timestamp'] = @timestamp
23
+ JSON.generate(data)
19
24
  end
20
25
  end
21
26
 
@@ -32,6 +37,155 @@ class RowAffected
32
37
  attr_reader :row_affected
33
38
  end
34
39
 
40
+ class Score
41
+ def initialize(id, score)
42
+ @id = id
43
+ @score = score
44
+ end
45
+
46
+ def self.from_json(string)
47
+ data = JSON.load string
48
+ data.map { |h| Score.new(h['Id'], h['Score']) }
49
+ end
50
+
51
+ attr_reader :id, :score
52
+ end
53
+
54
+ class User
55
+ def initialize(user_id:, labels: nil, comment: nil)
56
+ @user_id = user_id
57
+ @labels = labels
58
+ @comment = comment
59
+ end
60
+
61
+ def to_h
62
+ { 'UserId' => @user_id, 'Labels' => @labels, 'Comment' => @comment }
63
+ end
64
+
65
+ def to_json(*_args)
66
+ JSON.generate(to_h)
67
+ end
68
+
69
+ def self.from_json(string)
70
+ h = JSON.load string
71
+ User.new(user_id: h['UserId'], labels: h['Labels'], comment: h['Comment'])
72
+ end
73
+
74
+ attr_reader :user_id, :labels, :comment
75
+ end
76
+
77
+ class UserPatch
78
+ def initialize(labels: nil, comment: nil)
79
+ @labels = labels
80
+ @comment = comment
81
+ end
82
+
83
+ def to_json(*_args)
84
+ data = {}
85
+ data['Labels'] = @labels unless @labels.nil?
86
+ data['Comment'] = @comment unless @comment.nil?
87
+ JSON.generate(data)
88
+ end
89
+ end
90
+
91
+ class UserIterator
92
+ def initialize(cursor, users)
93
+ @cursor = cursor
94
+ @users = users
95
+ end
96
+
97
+ def self.from_json(string)
98
+ h = JSON.load string
99
+ users = (h['Users'] || []).map { |u| User.new(user_id: u['UserId'], labels: u['Labels'], comment: u['Comment']) }
100
+ UserIterator.new(h['Cursor'], users)
101
+ end
102
+
103
+ attr_reader :cursor, :users
104
+ end
105
+
106
+ class Item
107
+ def initialize(item_id:, is_hidden: nil, labels: nil, categories: nil, timestamp: nil, comment: nil)
108
+ @item_id = item_id
109
+ @is_hidden = is_hidden
110
+ @labels = labels
111
+ @categories = categories
112
+ @timestamp = timestamp
113
+ @comment = comment
114
+ end
115
+
116
+ def to_h
117
+ data = { 'ItemId' => @item_id }
118
+ data['IsHidden'] = @is_hidden unless @is_hidden.nil?
119
+ data['Labels'] = @labels unless @labels.nil?
120
+ data['Categories'] = @categories unless @categories.nil?
121
+ data['Timestamp'] = @timestamp unless @timestamp.nil?
122
+ data['Comment'] = @comment unless @comment.nil?
123
+ data
124
+ end
125
+
126
+ def to_json(*_args)
127
+ JSON.generate(to_h)
128
+ end
129
+
130
+ def self.from_json(string)
131
+ h = JSON.load string
132
+ Item.new(
133
+ item_id: h['ItemId'],
134
+ is_hidden: h['IsHidden'],
135
+ labels: h['Labels'],
136
+ categories: h['Categories'],
137
+ timestamp: h['Timestamp'],
138
+ comment: h['Comment']
139
+ )
140
+ end
141
+
142
+ attr_reader :item_id, :is_hidden, :labels, :categories, :timestamp, :comment
143
+ end
144
+
145
+ class ItemPatch
146
+ def initialize(is_hidden: nil, categories: nil, timestamp: nil, labels: nil, comment: nil)
147
+ @is_hidden = is_hidden
148
+ @categories = categories
149
+ @timestamp = timestamp
150
+ @labels = labels
151
+ @comment = comment
152
+ end
153
+
154
+ def to_json(*_args)
155
+ data = {}
156
+ data['IsHidden'] = @is_hidden unless @is_hidden.nil?
157
+ data['Categories'] = @categories unless @categories.nil?
158
+ data['Timestamp'] = @timestamp unless @timestamp.nil?
159
+ data['Labels'] = @labels unless @labels.nil?
160
+ data['Comment'] = @comment unless @comment.nil?
161
+ JSON.generate(data)
162
+ end
163
+ end
164
+
165
+ class ItemIterator
166
+ def initialize(cursor, items)
167
+ @cursor = cursor
168
+ @items = items
169
+ end
170
+
171
+ def self.from_json(string)
172
+ h = JSON.load string
173
+ items = (h['Items'] || []).map do |it|
174
+ Item.new(
175
+ item_id: it['ItemId'],
176
+ is_hidden: it['IsHidden'],
177
+ labels: it['Labels'],
178
+ categories: it['Categories'],
179
+ timestamp: it['Timestamp'],
180
+ comment: it['Comment']
181
+ )
182
+ end
183
+ ItemIterator.new(h['Cursor'], items)
184
+ end
185
+
186
+ attr_reader :cursor, :items
187
+ end
188
+
35
189
  class Gorse
36
190
  def initialize(endpoint, api_key = "")
37
191
  @endpoint = endpoint
@@ -39,17 +193,143 @@ class Gorse
39
193
  end
40
194
 
41
195
  def insert_feedback(feedback)
42
- uri = URI("#{@endpoint}/api/feedback")
43
- response = Net::HTTP::post(uri, JSON.generate(feedback), {
44
- 'Content-Type' => 'application/json',
45
- 'Accept' => 'application/json'
46
- })
47
- RowAffected.from_json(response.body)
48
- end
49
-
50
- def get_recommend(user_id)
51
- uri = URI("#{@endpoint}/api/recommend/#{user_id}")
52
- response = Net::HTTP::get(uri)
53
- JSON.parse(response)
196
+ response = request('POST', '/api/feedback', feedback)
197
+ RowAffected.from_json(response)
198
+ end
199
+
200
+ def list_feedbacks(feedback_type, user_id)
201
+ JSON.parse(request('GET', "/api/user/#{escape(user_id)}/feedback/#{escape(feedback_type)}"))
202
+ end
203
+
204
+ def delete_feedback(feedback_type, user_id, item_id)
205
+ RowAffected.from_json(request('DELETE', "/api/feedback/#{escape(feedback_type)}/#{escape(user_id)}/#{escape(item_id)}"))
206
+ end
207
+
208
+ def delete_feedbacks(user_id, item_id)
209
+ RowAffected.from_json(request('DELETE', "/api/feedback/#{escape(user_id)}/#{escape(item_id)}"))
210
+ end
211
+
212
+ def get_recommend(user_id, category: nil, n: nil, offset: nil)
213
+ if category.nil? && n.nil? && offset.nil?
214
+ return JSON.parse(request('GET', "/api/recommend/#{escape(user_id)}"))
215
+ end
216
+ cat_seg = (category || '').to_s
217
+ qs = []
218
+ qs << ["n", n] unless n.nil?
219
+ qs << ["offset", offset] unless offset.nil?
220
+ query = qs.map { |k, v| "#{k}=#{URI.encode_www_form_component(v.to_s)}" }.join('&')
221
+ path = "/api/recommend/#{escape(user_id)}/#{cat_seg}"
222
+ path += "?#{query}" unless query.empty?
223
+ JSON.parse(request('GET', path))
224
+ end
225
+
226
+ def get_latest_items(user_id: nil, category: nil, n:, offset: 0)
227
+ category_path = category && !category.empty? ? "/#{escape(category)}" : ''
228
+ qs = [["n", n], ["offset", offset]]
229
+ qs << ["user-id", user_id] unless user_id.nil? || user_id.empty?
230
+ query = qs.map { |k, v| "#{k}=#{URI.encode_www_form_component(v.to_s)}" }.join('&')
231
+ path = "/api/latest#{category_path}?#{query}"
232
+ JSON.parse(request('GET', path))
233
+ end
234
+
235
+ def session_recommend(feedbacks, n:)
236
+ path = "/api/session/recommend?n=#{n}"
237
+ JSON.parse(request('POST', path, feedbacks))
238
+ end
239
+
240
+ def get_neighbors(item_id, n:)
241
+ JSON.parse(request('GET', "/api/item/#{escape(item_id)}/neighbors?n=#{n}"))
242
+ end
243
+
244
+ def get_neighbors_category(item_id, category:, n:, offset: 0)
245
+ path = "/api/item/#{escape(item_id)}/neighbors/#{escape(category)}?n=#{n}&offset=#{offset}"
246
+ JSON.parse(request('GET', path))
247
+ end
248
+
249
+ def get_neighbors_users(user_id, n:, offset: 0)
250
+ path = "/api/user/#{escape(user_id)}/neighbors?n=#{n}&offset=#{offset}"
251
+ JSON.parse(request('GET', path))
252
+ end
253
+
254
+ # User APIs
255
+ def insert_user(user)
256
+ RowAffected.from_json(request('POST', '/api/user', user))
257
+ end
258
+
259
+ def insert_users(users)
260
+ RowAffected.from_json(request('POST', '/api/users', users))
261
+ end
262
+
263
+ def update_user(user_id, user_patch)
264
+ RowAffected.from_json(request('PATCH', "/api/user/#{escape(user_id)}", user_patch))
265
+ end
266
+
267
+ def get_user(user_id)
268
+ JSON.parse(request('GET', "/api/user/#{escape(user_id)}"))
269
+ end
270
+
271
+ def get_users(n:, cursor: '')
272
+ JSON.parse(request('GET', "/api/users?n=#{n}&cursor=#{URI.encode_www_form_component(cursor)}"))
273
+ end
274
+
275
+ def delete_user(user_id)
276
+ RowAffected.from_json(request('DELETE', "/api/user/#{escape(user_id)}"))
277
+ end
278
+
279
+ # Item APIs
280
+ def insert_item(item)
281
+ RowAffected.from_json(request('POST', '/api/item', item))
282
+ end
283
+
284
+ def insert_items(items)
285
+ RowAffected.from_json(request('POST', '/api/items', items))
286
+ end
287
+
288
+ def update_item(item_id, item_patch)
289
+ RowAffected.from_json(request('PATCH', "/api/item/#{escape(item_id)}", item_patch))
290
+ end
291
+
292
+ def get_item(item_id)
293
+ JSON.parse(request('GET', "/api/item/#{escape(item_id)}"))
294
+ end
295
+
296
+ def get_items(n:, cursor: '')
297
+ JSON.parse(request('GET', "/api/items?n=#{n}&cursor=#{URI.encode_www_form_component(cursor)}"))
298
+ end
299
+
300
+ def delete_item(item_id)
301
+ RowAffected.from_json(request('DELETE', "/api/item/#{escape(item_id)}"))
302
+ end
303
+
304
+ private
305
+
306
+ def escape(s)
307
+ URI.encode_www_form_component(s.to_s)
308
+ end
309
+
310
+ def request(method, path, body = nil)
311
+ base = @endpoint.end_with?('/') ? @endpoint : @endpoint + '/'
312
+ uri = URI.join(base, path.sub(/^\//, ''))
313
+ headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
314
+ headers['X-API-Key'] = @api_key if @api_key && !@api_key.empty?
315
+
316
+ req = case method.upcase
317
+ when 'GET' then Net::HTTP::Get.new(uri, headers)
318
+ when 'POST' then Net::HTTP::Post.new(uri, headers)
319
+ when 'PATCH' then Net::HTTP::Patch.new(uri, headers)
320
+ when 'DELETE' then Net::HTTP::Delete.new(uri, headers)
321
+ else raise "Unsupported method #{method}"
322
+ end
323
+ if body && %w[POST PATCH].include?(method.upcase)
324
+ payload = body.is_a?(String) ? body : JSON.generate(body)
325
+ req.body = payload
326
+ end
327
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |h|
328
+ h.request(req)
329
+ end
330
+ unless response.is_a?(Net::HTTPSuccess)
331
+ raise "HTTP #{response.code}: #{response.body}"
332
+ end
333
+ response.body
54
334
  end
55
335
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gorse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zhenghao Zhang
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-01 00:00:00.000000000 Z
11
+ date: 2025-11-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby SDK for Gorse recommender system
14
14
  email: zhangzhenghao@hotmail.com
@@ -21,7 +21,7 @@ homepage: https://gorse.io/
21
21
  licenses:
22
22
  - Apache 2
23
23
  metadata: {}
24
- post_install_message:
24
+ post_install_message:
25
25
  rdoc_options: []
26
26
  require_paths:
27
27
  - lib
@@ -36,8 +36,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
36
36
  - !ruby/object:Gem::Version
37
37
  version: '0'
38
38
  requirements: []
39
- rubygems_version: 3.0.3.1
40
- signing_key:
39
+ rubygems_version: 3.3.27
40
+ signing_key:
41
41
  specification_version: 4
42
42
  summary: Ruby SDK for Gorse recommender system
43
43
  test_files: []