recombee_api_client 1.1.0 → 1.2.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -4
  3. data/lib/recombee_api_client.rb +55 -36
  4. data/lib/recombee_api_client/api/add_bookmark.rb +2 -0
  5. data/lib/recombee_api_client/api/add_cart_addition.rb +2 -0
  6. data/lib/recombee_api_client/api/add_detail_view.rb +2 -0
  7. data/lib/recombee_api_client/api/add_group.rb +2 -0
  8. data/lib/recombee_api_client/api/add_item.rb +2 -0
  9. data/lib/recombee_api_client/api/add_item_property.rb +2 -0
  10. data/lib/recombee_api_client/api/add_purchase.rb +2 -0
  11. data/lib/recombee_api_client/api/add_rating.rb +2 -0
  12. data/lib/recombee_api_client/api/add_series.rb +2 -0
  13. data/lib/recombee_api_client/api/add_user.rb +2 -0
  14. data/lib/recombee_api_client/api/batch.rb +2 -0
  15. data/lib/recombee_api_client/api/delete_bookmark.rb +17 -5
  16. data/lib/recombee_api_client/api/delete_cart_addition.rb +17 -5
  17. data/lib/recombee_api_client/api/delete_detail_view.rb +17 -5
  18. data/lib/recombee_api_client/api/delete_group.rb +2 -0
  19. data/lib/recombee_api_client/api/delete_item.rb +2 -0
  20. data/lib/recombee_api_client/api/delete_item_property.rb +2 -0
  21. data/lib/recombee_api_client/api/delete_purchase.rb +17 -5
  22. data/lib/recombee_api_client/api/delete_rating.rb +17 -5
  23. data/lib/recombee_api_client/api/delete_series.rb +2 -0
  24. data/lib/recombee_api_client/api/delete_user.rb +2 -0
  25. data/lib/recombee_api_client/api/get_item_property_info.rb +2 -0
  26. data/lib/recombee_api_client/api/get_item_values.rb +2 -0
  27. data/lib/recombee_api_client/api/insert_to_group.rb +2 -0
  28. data/lib/recombee_api_client/api/insert_to_series.rb +2 -0
  29. data/lib/recombee_api_client/api/item_based_recommendation.rb +19 -3
  30. data/lib/recombee_api_client/api/list_group_items.rb +2 -0
  31. data/lib/recombee_api_client/api/list_groups.rb +2 -0
  32. data/lib/recombee_api_client/api/list_item_bookmarks.rb +2 -0
  33. data/lib/recombee_api_client/api/list_item_cart_additions.rb +2 -0
  34. data/lib/recombee_api_client/api/list_item_detail_views.rb +2 -0
  35. data/lib/recombee_api_client/api/list_item_properties.rb +2 -0
  36. data/lib/recombee_api_client/api/list_item_purchases.rb +2 -0
  37. data/lib/recombee_api_client/api/list_item_ratings.rb +2 -0
  38. data/lib/recombee_api_client/api/list_items.rb +2 -0
  39. data/lib/recombee_api_client/api/list_series.rb +2 -0
  40. data/lib/recombee_api_client/api/list_series_items.rb +2 -0
  41. data/lib/recombee_api_client/api/list_user_bookmarks.rb +2 -0
  42. data/lib/recombee_api_client/api/list_user_cart_additions.rb +2 -0
  43. data/lib/recombee_api_client/api/list_user_detail_views.rb +2 -0
  44. data/lib/recombee_api_client/api/list_user_purchases.rb +2 -0
  45. data/lib/recombee_api_client/api/list_user_ratings.rb +2 -0
  46. data/lib/recombee_api_client/api/list_users.rb +2 -0
  47. data/lib/recombee_api_client/api/merge_users.rb +2 -0
  48. data/lib/recombee_api_client/api/remove_from_group.rb +2 -0
  49. data/lib/recombee_api_client/api/remove_from_series.rb +2 -0
  50. data/lib/recombee_api_client/api/reset_database.rb +3 -1
  51. data/lib/recombee_api_client/api/set_item_values.rb +2 -0
  52. data/lib/recombee_api_client/api/user_based_recommendation.rb +19 -3
  53. data/lib/recombee_api_client/version.rb +1 -1
  54. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 502d27b927763c5c54e373aca3db137df0198683
4
- data.tar.gz: 0dea5323ad9a5b304bd72a8fe20f590ce8002103
3
+ metadata.gz: 11704e7192a3cf0b6f8122a3049608247b5c274f
4
+ data.tar.gz: 1841fd3587cde54649877aaf150f798649264072
5
5
  SHA512:
6
- metadata.gz: 2787a7a249bf23cb1df46cb9047c6e36e13227677a936c79a87a680547c21829850de8ec2c3dda9083dd9351abd35c3fbdea0d0607da93dae85669138ff6c151
7
- data.tar.gz: 117af7984b724ada34b5ffac9bbf03e3efbded675733daf76e7ceb79a25b494db5285c8408a33c089f68117940879030cdac28d6e1a53714b627a0324c65734a
6
+ metadata.gz: 5f61d16bcb1bd799376ba4e4c984ab806960bb14ccd3f7c2c22754bdcdcc4228107873a72a88c15a1f186be7f19e256a38041fe72392778be5a452e26589e97d
7
+ data.tar.gz: 21109e33ba9b98f43a62b512c825420c4f1e1544f067b109a1195386785d488365229f94bcf618d271a0b3992dafe15e188c885e5389cd42c7f6653e9cf2ba81
data/README.md CHANGED
@@ -49,7 +49,7 @@ begin
49
49
  puts 'Send items'
50
50
  client.send(Batch.new(my_items.map { |itemId| AddItem.new(itemId) }))
51
51
  puts 'Send purchases'
52
- client.send(Batch.new(my_purchases.map { |p| AddPurchase.new(p['userId'], p['itemId'], 0) }))
52
+ client.send(Batch.new(my_purchases.map { |p| AddPurchase.new(p['userId'], p['itemId']) }))
53
53
 
54
54
  # Get recommendations for user 'user-25'
55
55
  puts 'Recommend for a user'
@@ -62,8 +62,6 @@ end
62
62
 
63
63
  ### Using property values
64
64
  ```ruby
65
- #!/usr/bin/env ruby
66
-
67
65
  require 'recombee_api_client'
68
66
  include RecombeeApiClient
69
67
 
@@ -107,7 +105,7 @@ requests = []
107
105
  user_ids = (1..NUM).map{|i| "user-#{i}"}
108
106
  user_ids = user_ids.select { |_| rand(0.0..1.0) < PROBABILITY_PURCHASED }
109
107
  # Use cascadeCreate to create unexisting users
110
- user_ids.each { |user_id| requests.push(AddPurchase.new(user_id, item_id, 0, 'cascadeCreate' => true)) }
108
+ user_ids.each { |user_id| requests.push(AddPurchase.new(user_id, item_id, 'cascadeCreate' => true)) }
111
109
  end
112
110
 
113
111
  # Send purchases to the recommender system
@@ -1,6 +1,6 @@
1
1
  require 'recombee_api_client/version'
2
2
  require 'securerandom'
3
- require 'digest/hmac'
3
+ require 'openssl'
4
4
  require 'httparty'
5
5
  require 'json'
6
6
  require 'open-uri'
@@ -11,85 +11,103 @@ require 'recombee_api_client/errors'
11
11
  Gem.find_files('recombee_api_client/api/*.rb').each { |path| require path }
12
12
 
13
13
  module RecombeeApiClient
14
+ ##
15
+ # Client for sending requests to Recombee recommender system
14
16
  class RecombeeClient
15
17
  include HTTParty
16
18
 
17
- def initialize(account, token, options = {})
19
+ ##
20
+ # - +account+ -> Name of your account at Recombee
21
+ # - +token+ -> Secret token obtained from Recombee for signing requests
22
+ # - +protocol+ -> Default protocol for sending requests. Possible values: 'http', 'https'.
23
+ def initialize(account, token, protocol = 'http', options = {})
18
24
  @account = account
19
25
  @token = token
20
- @base_uri = options[:base_uri] ||= 'https://rapi.recombee.com'
26
+ @protocol = protocol
27
+ @base_uri = ENV['RAPI_URI'] if ENV.key? 'RAPI_URI'
28
+ @base_uri||= options[:base_uri]
29
+ @base_uri||= 'rapi.recombee.com'
21
30
  end
22
31
 
32
+ ##
33
+ # - +request+ -> ApiRequest to be sent to Recombee recommender
23
34
  def send(request)
24
- @request = request
25
- uri = request.path
26
- uri.slice! ('/{databaseId}/')
27
- uri = URI.escape uri
28
35
  timeout = request.timeout / 1000
36
+ uri = process_request_uri(request)
37
+ uri = sign_url(uri)
38
+ protocol = request.ensure_https ? 'https' : @protocol
39
+ uri = protocol + '://' + @base_uri + uri
29
40
  # puts uri
30
41
  begin
31
42
  case request.method
32
43
  when :put
33
- hmac_put(uri, timeout)
44
+ put(request, uri, timeout)
34
45
  when :get
35
- hmac_get(uri, timeout)
46
+ get(request, uri, timeout)
36
47
  when :post
37
- hmac_post(uri, timeout, request.body_parameters.to_json)
48
+ post(request, uri, timeout)
38
49
  when :delete
39
- hmac_delete(uri, timeout)
50
+ delete(request, uri, timeout)
40
51
  end
41
52
  rescue Timeout::Error
42
- fail ApiTimeout.new(@request)
53
+ fail ApiTimeout.new(request)
43
54
  end
44
55
  end
45
56
 
46
57
  private
47
58
 
48
- def hmac_put(uri, timeout, options = {})
49
- r = self.class.put(sign_url(uri), query: options, timeout: timeout)
50
- check_errors r
51
- r.body
59
+ def put(request, uri, timeout)
60
+ response = self.class.put(uri, timeout: timeout)
61
+ check_errors(response, request)
62
+ response.body
52
63
  end
53
64
 
54
- def hmac_get(uri, timeout, options = {})
55
- r = self.class.get(sign_url(uri), query: options, timeout: timeout)
56
- check_errors r
57
- JSON.parse(r.body)
65
+ def get(request, uri, timeout)
66
+ response = self.class.get(uri, timeout: timeout)
67
+ check_errors(response, request)
68
+ JSON.parse(response.body)
58
69
  end
59
70
 
60
- def hmac_post(uri, timeout, options = {})
61
- url = sign_url(uri)
71
+ def post(request, uri, timeout)
62
72
  # pass arguments in body
63
- r = self.class.post(url, body: options,
73
+ response = self.class.post(uri, body: request.body_parameters.to_json,
64
74
  headers: { 'Content-Type' => 'application/json' },
65
75
  timeout: timeout)
66
- check_errors r
76
+ check_errors(response, request)
67
77
  begin
68
- return JSON.parse(r.body)
78
+ return JSON.parse(response.body)
69
79
  rescue JSON::ParserError
70
- return r.body
80
+ return response.body
71
81
  end
72
82
  end
73
83
 
74
- def hmac_delete(uri, timeout, options = {})
75
- r = self.class.delete(sign_url(uri), query: options, timeout: timeout)
76
- check_errors r
77
- r.body
84
+ def delete(request, uri, timeout)
85
+ response = self.class.delete(uri, timeout: timeout)
86
+ check_errors(response, request)
87
+ response.body
78
88
  end
79
89
 
80
- def check_errors(response)
90
+ def check_errors(response, request)
81
91
  status_code = response.code
82
92
  return if status_code == 200 || status_code == 201
83
- fail ResponseError.new(@request, status_code, response.body)
93
+ fail ResponseError.new(request, status_code, response.body)
94
+ end
95
+
96
+ def process_request_uri(request)
97
+ uri = request.path
98
+ uri.slice! ('/{databaseId}/')
99
+ uri = URI.escape uri
100
+ uri
84
101
  end
85
102
 
86
103
  # Sign request with HMAC, request URI must be exacly the same
87
104
  # We have 30s to complete request with this token
88
- def sign_url(req)
89
- uri = "/#{@account}/#{req}"
105
+ def sign_url(req_part)
106
+ uri = "/#{@account}/#{req_part}"
90
107
  time = hmac_time(uri)
91
108
  sign = hmac_sign(uri, time)
92
- @base_uri + uri + time + "&hmac_sign=#{sign}"
109
+ res = uri + time + "&hmac_sign=#{sign}"
110
+ res
93
111
  end
94
112
 
95
113
  def hmac_time(uri)
@@ -99,7 +117,8 @@ module RecombeeApiClient
99
117
 
100
118
  def hmac_sign(uri, time)
101
119
  url = uri + time
102
- Digest::HMAC.hexdigest(url, @token, Digest::SHA1)
120
+ digest = OpenSSL::Digest.new('sha1')
121
+ OpenSSL::HMAC.hexdigest(digest, @token, url)
103
122
  end
104
123
  end
105
124
  end
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddBookmark < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp, :cascade_create
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -29,6 +30,7 @@ module RecombeeApiClient
29
30
  @cascade_create = optional['cascadeCreate']
30
31
  @optional = optional
31
32
  @timeout = 1000
33
+ @ensure_https = false
32
34
  @optional.each do |par, _|
33
35
  fail UnknownOptionalParameter.new(par) unless ["timestamp","cascadeCreate"].include? par
34
36
  end
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddCartAddition < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp, :cascade_create
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -29,6 +30,7 @@ module RecombeeApiClient
29
30
  @cascade_create = optional['cascadeCreate']
30
31
  @optional = optional
31
32
  @timeout = 1000
33
+ @ensure_https = false
32
34
  @optional.each do |par, _|
33
35
  fail UnknownOptionalParameter.new(par) unless ["timestamp","cascadeCreate"].include? par
34
36
  end
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddDetailView < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp, :duration, :cascade_create
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -31,6 +32,7 @@ module RecombeeApiClient
31
32
  @cascade_create = optional['cascadeCreate']
32
33
  @optional = optional
33
34
  @timeout = 1000
35
+ @ensure_https = false
34
36
  @optional.each do |par, _|
35
37
  fail UnknownOptionalParameter.new(par) unless ["timestamp","duration","cascadeCreate"].include? par
36
38
  end
@@ -11,6 +11,7 @@ module RecombeeApiClient
11
11
  class AddGroup < ApiRequest
12
12
  attr_reader :group_id
13
13
  attr_accessor :timeout
14
+ attr_accessor :ensure_https
14
15
 
15
16
  ##
16
17
  # * *Required arguments*
@@ -19,6 +20,7 @@ module RecombeeApiClient
19
20
  def initialize(group_id)
20
21
  @group_id = group_id
21
22
  @timeout = 1000
23
+ @ensure_https = false
22
24
  end
23
25
 
24
26
  # HTTP method
@@ -14,6 +14,7 @@ module RecombeeApiClient
14
14
  class AddItem < ApiRequest
15
15
  attr_reader :item_id
16
16
  attr_accessor :timeout
17
+ attr_accessor :ensure_https
17
18
 
18
19
  ##
19
20
  # * *Required arguments*
@@ -22,6 +23,7 @@ module RecombeeApiClient
22
23
  def initialize(item_id)
23
24
  @item_id = item_id
24
25
  @timeout = 1000
26
+ @ensure_https = false
25
27
  end
26
28
 
27
29
  # HTTP method
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddItemProperty < ApiRequest
13
13
  attr_reader :property_name, :type
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -24,6 +25,7 @@ module RecombeeApiClient
24
25
  @property_name = property_name
25
26
  @type = type
26
27
  @timeout = 1000
28
+ @ensure_https = false
27
29
  end
28
30
 
29
31
  # HTTP method
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddPurchase < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp, :cascade_create
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -29,6 +30,7 @@ module RecombeeApiClient
29
30
  @cascade_create = optional['cascadeCreate']
30
31
  @optional = optional
31
32
  @timeout = 1000
33
+ @ensure_https = false
32
34
  @optional.each do |par, _|
33
35
  fail UnknownOptionalParameter.new(par) unless ["timestamp","cascadeCreate"].include? par
34
36
  end
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddRating < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp, :rating, :cascade_create
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -31,6 +32,7 @@ module RecombeeApiClient
31
32
  @cascade_create = optional['cascadeCreate']
32
33
  @optional = optional
33
34
  @timeout = 1000
35
+ @ensure_https = false
34
36
  @optional.each do |par, _|
35
37
  fail UnknownOptionalParameter.new(par) unless ["timestamp","cascadeCreate"].include? par
36
38
  end
@@ -11,6 +11,7 @@ module RecombeeApiClient
11
11
  class AddSeries < ApiRequest
12
12
  attr_reader :series_id
13
13
  attr_accessor :timeout
14
+ attr_accessor :ensure_https
14
15
 
15
16
  ##
16
17
  # * *Required arguments*
@@ -19,6 +20,7 @@ module RecombeeApiClient
19
20
  def initialize(series_id)
20
21
  @series_id = series_id
21
22
  @timeout = 1000
23
+ @ensure_https = false
22
24
  end
23
25
 
24
26
  # HTTP method
@@ -12,6 +12,7 @@ module RecombeeApiClient
12
12
  class AddUser < ApiRequest
13
13
  attr_reader :user_id
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
@@ -20,6 +21,7 @@ module RecombeeApiClient
20
21
  def initialize(user_id)
21
22
  @user_id = user_id
22
23
  @timeout = 1000
24
+ @ensure_https = false
23
25
  end
24
26
 
25
27
  # HTTP method
@@ -9,6 +9,7 @@ module RecombeeApiClient
9
9
  class Batch < ApiRequest
10
10
  attr_reader :requests
11
11
  attr_accessor :timeout
12
+ attr_accessor :ensure_https
12
13
  ##
13
14
  # * *Required arguments*
14
15
  # - +requests+ -> Array of API requests.
@@ -17,6 +18,7 @@ module RecombeeApiClient
17
18
  @requests = requests
18
19
  @body_parameters = requests_to_batch_hash
19
20
  @timeout = requests.map{|r| r.timeout}.reduce(:+)
21
+ @ensure_https = true
20
22
  end
21
23
 
22
24
  # HTTP method
@@ -12,18 +12,26 @@ module RecombeeApiClient
12
12
  class DeleteBookmark < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
18
19
  # - +user_id+ -> ID of the user who made the bookmark.
19
20
  # - +item_id+ -> ID of the item of which was bookmarked.
20
- # - +timestamp+ -> Unix timestamp of the bookmark.
21
21
  #
22
- def initialize(user_id, item_id, timestamp)
22
+ # * *Optional arguments (given as hash optional)*
23
+ # - +timestamp+ -> Unix timestamp of the bookmark. If the `timestamp` is omitted, then all the bookmarks with given `userId` and `itemId` are deleted.
24
+ #
25
+ def initialize(user_id, item_id, optional = {})
23
26
  @user_id = user_id
24
27
  @item_id = item_id
25
- @timestamp = timestamp
28
+ @timestamp = optional['timestamp']
29
+ @optional = optional
26
30
  @timeout = 1000
31
+ @ensure_https = false
32
+ @optional.each do |par, _|
33
+ fail UnknownOptionalParameter.new(par) unless ["timestamp"].include? par
34
+ end
27
35
  end
28
36
 
29
37
  # HTTP method
@@ -43,7 +51,7 @@ module RecombeeApiClient
43
51
  params = {}
44
52
  params['userId'] = @user_id
45
53
  params['itemId'] = @item_id
46
- params['timestamp'] = @timestamp
54
+ params['timestamp'] = @optional['timestamp'] if @optional['timestamp']
47
55
  params
48
56
  end
49
57
 
@@ -54,7 +62,11 @@ module RecombeeApiClient
54
62
 
55
63
  # Relative path to the endpoint including query parameters
56
64
  def path
57
- p = "/{databaseId}/bookmarks/?userId=#{@user_id}&itemId=#{@item_id}&timestamp=#{@timestamp}"
65
+ p = "/{databaseId}/bookmarks/?userId=#{@user_id}&itemId=#{@item_id}"
66
+ if @optional.include? 'timestamp'
67
+ p += (p.include? '?') ? '&' : '?'
68
+ p += "timestamp=#{@optional['timestamp']}"
69
+ end
58
70
  p
59
71
  end
60
72
  end
@@ -12,18 +12,26 @@ module RecombeeApiClient
12
12
  class DeleteCartAddition < ApiRequest
13
13
  attr_reader :user_id, :item_id, :timestamp
14
14
  attr_accessor :timeout
15
+ attr_accessor :ensure_https
15
16
 
16
17
  ##
17
18
  # * *Required arguments*
18
19
  # - +user_id+ -> ID of the user who made the cart addition.
19
20
  # - +item_id+ -> ID of the item of which was added to cart.
20
- # - +timestamp+ -> Unix timestamp of the cart addition.
21
21
  #
22
- def initialize(user_id, item_id, timestamp)
22
+ # * *Optional arguments (given as hash optional)*
23
+ # - +timestamp+ -> Unix timestamp of the cart addition. If the `timestamp` is omitted, then all the cart additions with given `userId` and `itemId` are deleted.
24
+ #
25
+ def initialize(user_id, item_id, optional = {})
23
26
  @user_id = user_id
24
27
  @item_id = item_id
25
- @timestamp = timestamp
28
+ @timestamp = optional['timestamp']
29
+ @optional = optional
26
30
  @timeout = 1000
31
+ @ensure_https = false
32
+ @optional.each do |par, _|
33
+ fail UnknownOptionalParameter.new(par) unless ["timestamp"].include? par
34
+ end
27
35
  end
28
36
 
29
37
  # HTTP method
@@ -43,7 +51,7 @@ module RecombeeApiClient
43
51
  params = {}
44
52
  params['userId'] = @user_id
45
53
  params['itemId'] = @item_id
46
- params['timestamp'] = @timestamp
54
+ params['timestamp'] = @optional['timestamp'] if @optional['timestamp']
47
55
  params
48
56
  end
49
57
 
@@ -54,7 +62,11 @@ module RecombeeApiClient
54
62
 
55
63
  # Relative path to the endpoint including query parameters
56
64
  def path
57
- p = "/{databaseId}/cartadditions/?userId=#{@user_id}&itemId=#{@item_id}&timestamp=#{@timestamp}"
65
+ p = "/{databaseId}/cartadditions/?userId=#{@user_id}&itemId=#{@item_id}"
66
+ if @optional.include? 'timestamp'
67
+ p += (p.include? '?') ? '&' : '?'
68
+ p += "timestamp=#{@optional['timestamp']}"
69
+ end
58
70
  p
59
71
  end
60
72
  end