riak-client 1.0.5 → 1.1.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 (56) hide show
  1. data/.document +5 -0
  2. data/.gitignore +4 -3
  3. data/.rspec +1 -0
  4. data/Gemfile +1 -0
  5. data/RELEASE_NOTES.md +47 -0
  6. data/Rakefile +0 -1
  7. data/erl_src/riak_kv_test_backend.erl +34 -0
  8. data/lib/riak/client.rb +3 -1
  9. data/lib/riak/client/beefcake/messages.rb +49 -1
  10. data/lib/riak/client/beefcake/object_methods.rb +14 -21
  11. data/lib/riak/client/beefcake_protobuffs_backend.rb +58 -12
  12. data/lib/riak/client/decaying.rb +31 -23
  13. data/lib/riak/client/feature_detection.rb +88 -0
  14. data/lib/riak/client/http_backend.rb +27 -6
  15. data/lib/riak/client/http_backend/configuration.rb +13 -0
  16. data/lib/riak/client/http_backend/object_methods.rb +33 -25
  17. data/lib/riak/client/node.rb +7 -2
  18. data/lib/riak/client/protobuffs_backend.rb +54 -3
  19. data/lib/riak/client/search.rb +2 -2
  20. data/lib/riak/conflict.rb +13 -0
  21. data/lib/riak/locale/en.yml +2 -0
  22. data/lib/riak/map_reduce.rb +1 -1
  23. data/lib/riak/map_reduce/filter_builder.rb +2 -2
  24. data/lib/riak/map_reduce/results.rb +49 -0
  25. data/lib/riak/node/console.rb +17 -16
  26. data/lib/riak/node/generation.rb +9 -0
  27. data/lib/riak/rcontent.rb +168 -0
  28. data/lib/riak/robject.rb +37 -157
  29. data/lib/riak/util/escape.rb +5 -1
  30. data/lib/riak/version.rb +1 -1
  31. data/riak-client.gemspec +37 -5
  32. data/spec/fixtures/multipart-basic-conflict.txt +15 -0
  33. data/spec/fixtures/munchausen.txt +1033 -0
  34. data/spec/integration/riak/cluster_spec.rb +1 -1
  35. data/spec/integration/riak/http_backends_spec.rb +23 -2
  36. data/spec/integration/riak/node_spec.rb +2 -2
  37. data/spec/integration/riak/protobuffs_backends_spec.rb +17 -2
  38. data/spec/integration/riak/test_server_spec.rb +1 -1
  39. data/spec/integration/riak/threading_spec.rb +3 -3
  40. data/spec/riak/beefcake_protobuffs_backend_spec.rb +58 -25
  41. data/spec/riak/escape_spec.rb +3 -0
  42. data/spec/riak/feature_detection_spec.rb +61 -0
  43. data/spec/riak/http_backend/object_methods_spec.rb +4 -13
  44. data/spec/riak/http_backend_spec.rb +6 -5
  45. data/spec/riak/map_reduce_spec.rb +0 -5
  46. data/spec/riak/robject_spec.rb +12 -11
  47. data/spec/spec_helper.rb +3 -1
  48. data/spec/support/riak_test.rb +77 -0
  49. data/spec/support/search_corpus_setup.rb +18 -0
  50. data/spec/support/sometimes.rb +1 -1
  51. data/spec/support/test_server.rb +1 -1
  52. data/spec/support/unified_backend_examples.rb +53 -7
  53. data/spec/support/version_filter.rb +4 -11
  54. metadata +56 -22
  55. data/lib/riak/client/pool.rb +0 -180
  56. data/spec/riak/pool_spec.rb +0 -306
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore CHANGED
@@ -1,3 +1,7 @@
1
+ ## NOTE: This file must be manually kept in sync with the gemspec, but
2
+ ## should not normally need to be modified unless new top-level files
3
+ ## or directory trees are being added.
4
+
1
5
  ## MAC OS
2
6
  .DS_Store
3
7
 
@@ -18,9 +22,6 @@ coverage
18
22
  rdoc
19
23
  pkg
20
24
 
21
- ## This line is for the dirglob only
22
- pkg/*
23
-
24
25
  ## PROJECT::SPECIFIC
25
26
  _notes
26
27
  doc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
  gem 'bundler'
5
+ gem 'rake'
5
6
 
6
7
  group :guard do
7
8
  gem 'guard-rspec'
data/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Riak Ruby Client Release Notes
2
2
 
3
+ ## 1.1.0 Feature Release - 2012-11-07
4
+
5
+ Release 1.1.0 includes full Riak 1.2 compatibility, and includes
6
+ improvements to the handling of siblings, the node generation
7
+ tools, and resolves a number of important bugs.
8
+
9
+ Features:
10
+
11
+ * Client features are enabled or disabled based on the detected Riak
12
+ version.
13
+ * Riak 1.2 compatibility, including search and 2I over Protocol
14
+ Buffers.
15
+ * Phaseless MapReduce (which was available in 1.1) is allowed, using
16
+ feature detection to determine whether an exception is raised.
17
+ * Conditional store_object operations on Protocol Buffers use the
18
+ message features available since Riak 1.0.
19
+ * The integration test-suite can be run without generating a test
20
+ node, which lets us support riak_test.
21
+
22
+ Bugfixes:
23
+
24
+ * URL-escaping now allows some normally URI-safe characters to be
25
+ escaped.
26
+ * JRuby should be more reliable when attaching to a generated node's
27
+ console.
28
+ * The client backend pool has been extracted to the Innertube gem,
29
+ which is now a dependency.
30
+ * Fix a documentation issue around key-filters.
31
+ * Fix RSpec formatter and deprecation errors.
32
+ * Object siblings are now a separate class (RContent) rather than
33
+ being unclean copies of the parent RObject. If only one sibling
34
+ exists, the original accessors (e.g. `content_type`, `data`) will
35
+ behave as expected. When more than one sibling exists, they will
36
+ raise `Riak::Conflict`. This should prevent unintentional storing of
37
+ unresolved objects back into Riak as `multipart/mixed` values.
38
+ * `Riak::Client#ssl=` won't blow away existing `ssl_options` if set to
39
+ `true`.
40
+ * Generated nodes will ensure that the source's
41
+ `ssl_distribution.args_file` exists by invoking `riak chkconfig`.
42
+ * Copy fixes for the `$key` index on the memory/test backend from
43
+ riak_kv.
44
+ * The shape of MapReduce results will no longer be changed by the
45
+ Protocol Buffers backend, which manifested as kept phases without
46
+ results being removed from the return value. Implementing this
47
+ required all HTTP requests to use streaming, even if invoked without
48
+ a block.
49
+
3
50
  ## 1.0.5 Packaging Fix Release - 2012-10-12
4
51
 
5
52
  Release 1.0.5 fixes a bug with the RubyGems packaging that
data/Rakefile CHANGED
@@ -69,5 +69,4 @@ desc "Run All Specs (including slow specs)"
69
69
  RSpec::Core::RakeTask.new(:ci) do |spec|
70
70
  spec.rspec_opts = %w[--profile]
71
71
  end
72
-
73
72
  task :default => :ci
@@ -61,6 +61,7 @@
61
61
 
62
62
  -ifdef(TEST).
63
63
  -include_lib("eunit/include/eunit.hrl").
64
+ -compile([export_all]).
64
65
  -endif.
65
66
 
66
67
  -define(API_VERSION, 1).
@@ -427,6 +428,9 @@ fold_keys_fun(FoldKeysFun, {index, FilterBucket, {eq, <<"$bucket">>, _}}) ->
427
428
  fold_keys_fun(FoldKeysFun, {index, FilterBucket, {range, <<"$key">>, _, _}}) ->
428
429
  %% 2I range query on special $key field...
429
430
  fold_keys_fun(FoldKeysFun, {bucket, FilterBucket});
431
+ fold_keys_fun(FoldKeysFun, {index, FilterBucket, {eq, <<"$key">>, _}}) ->
432
+ %% 2I eq query on special $key field...
433
+ fold_keys_fun(FoldKeysFun, {bucket, FilterBucket});
430
434
  fold_keys_fun(FoldKeysFun, {index, _FilterBucket, _Query}) ->
431
435
  fun({{Bucket, _FilterField, _FilterTerm, Key}, _}, Acc) ->
432
436
  FoldKeysFun(Bucket, Key, Acc);
@@ -469,6 +473,8 @@ get_index_folder(Folder, Acc0, {index, Bucket, {range, <<"$key">>, Min, Max}}, D
469
473
  fun() ->
470
474
  key_range_folder(Folder, Acc0, DataRef, {Bucket, Min}, {Bucket, Min, Max})
471
475
  end;
476
+ get_index_folder(Folder, Acc0, {index, Bucket, {eq, <<"$key">>, Val}}, DataRef, IndexRef) ->
477
+ get_index_folder(Folder, Acc0, {index, Bucket, {range, <<"$key">>, Val, Val}}, DataRef, IndexRef);
472
478
  get_index_folder(Folder, Acc0, {index, Bucket, {eq, Field, Term}}, _, IndexRef) ->
473
479
  fun() ->
474
480
  index_range_folder(Folder, Acc0, IndexRef, {Bucket, Field, Term, undefined}, {Bucket, Field, Term, Term})
@@ -668,6 +674,34 @@ max_memory_test_() ->
668
674
  ?_assertEqual({ok, Value2, State2}, get(Bucket, Key2, State2))
669
675
  ].
670
676
 
677
+ regression_367_key_range_test_() ->
678
+ {ok, State} = start(142, []),
679
+ Keys = [begin
680
+ Bin = list_to_binary(integer_to_list(I)),
681
+ if I < 10 ->
682
+ <<"obj0", Bin/binary>>;
683
+ true -> <<"obj", Bin/binary>>
684
+ end
685
+ end || I <- lists:seq(1,30) ],
686
+ Bucket = <<"keyrange">>,
687
+ Value = <<"foobarbaz">>,
688
+ State1 = lists:foldl(fun(Key, IState) ->
689
+ {ok, NewState} = put(Bucket, Key, [], Value, IState),
690
+ NewState
691
+ end, State, Keys),
692
+ Folder = fun(_B, K, Acc) ->
693
+ Acc ++ [K]
694
+ end,
695
+ [
696
+ ?_assertEqual({ok, [<<"obj01">>]}, fold_keys(Folder, [], [{index, Bucket, {range, <<"$key">>, <<"obj01">>, <<"obj01">>}}], State1)),
697
+ ?_assertEqual({ok, [<<"obj10">>,<<"obj11">>]}, fold_keys(Folder, [], [{index, Bucket, {range, <<"$key">>, <<"obj10">>, <<"obj11">>}}], State1)),
698
+ ?_assertEqual({ok, [<<"obj01">>]}, fold_keys(Folder, [], [{index, Bucket, {range, <<"$key">>, <<"obj00">>, <<"obj01">>}}], State1)),
699
+ ?_assertEqual({ok, lists:sort(Keys)}, fold_keys(Folder, [], [{index, Bucket, {range, <<"$key">>, <<"obj0">>, <<"obj31">>}}], State1)),
700
+ ?_assertEqual({ok, []}, fold_keys(Folder, [], [{index, Bucket, {range, <<"$key">>, <<"obj31">>, <<"obj32">>}}], State1)),
701
+ ?_assertEqual({ok, [<<"obj01">>]}, fold_keys(Folder, [], [{index, Bucket, {eq, <<"$key">>, <<"obj01">>}}], State1)),
702
+ ?_assertEqual(ok, stop(State1))
703
+ ].
704
+
671
705
  -ifdef(EQC).
672
706
 
673
707
  eqc_test_() ->
data/lib/riak/client.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  require 'tempfile'
2
2
  require 'delegate'
3
+ require 'innertube'
3
4
  require 'riak'
4
5
  require 'riak/util/translation'
5
6
  require 'riak/util/escape'
6
7
  require 'riak/failed_request'
7
- require 'riak/client/pool'
8
8
  require 'riak/client/decaying'
9
9
  require 'riak/client/node'
10
10
  require 'riak/client/search'
@@ -47,6 +47,8 @@ module Riak
47
47
  SystemCallError,
48
48
  ]
49
49
 
50
+ Pool = ::Innertube::Pool
51
+
50
52
  # @return [String] The protocol to use for the Riak endpoint
51
53
  attr_reader :protocol
52
54
 
@@ -89,7 +89,7 @@ module Riak
89
89
  required :content, RpbContent, 4
90
90
  optional :w, :uint32, 5
91
91
  optional :dw, :uint32, 6
92
- optional :returnbody, :bool, 7
92
+ optional :returnbody, :bool, 7
93
93
  optional :pw, :uint32, 8
94
94
  optional :if_not_modified, :bool, 9
95
95
  optional :if_none_match, :bool, 10
@@ -160,6 +160,54 @@ module Riak
160
160
  optional :response, :bytes, 2
161
161
  optional :done, :bool, 3
162
162
  end
163
+
164
+ class RpbIndexReq
165
+ include Beefcake::Message
166
+ module IndexQueryType
167
+ EQ = 0
168
+ RANGE = 1
169
+ end
170
+
171
+ required :bucket, :bytes, 1
172
+ required :index, :bytes, 2
173
+ required :qtype, IndexQueryType, 3
174
+ optional :key, :bytes, 4
175
+ optional :range_min, :bytes, 5
176
+ optional :range_max, :bytes, 6
177
+ end
178
+
179
+ class RpbIndexResp
180
+ include Beefcake::Message
181
+ repeated :keys, :bytes, 1
182
+ end
183
+
184
+ class RpbSearchDoc
185
+ include Beefcake::Message
186
+ # We have to name this differently than the .proto file does
187
+ # because Beefcake uses 'fields' as an instance method.
188
+ repeated :properties, RpbPair, 1
189
+ end
190
+
191
+ class RpbSearchQueryReq
192
+ include Beefcake::Message
193
+ required :q, :bytes, 1
194
+ required :index, :bytes, 2
195
+ optional :rows, :uint32, 3
196
+ optional :start, :uint32, 4
197
+ optional :sort, :bytes, 5
198
+ optional :filter, :bytes, 6
199
+ optional :df, :bytes, 7
200
+ optional :op, :bytes, 8
201
+ repeated :fl, :bytes, 9
202
+ optional :presort, :bytes, 10
203
+ end
204
+
205
+ class RpbSearchQueryResp
206
+ include Beefcake::Message
207
+ repeated :docs, RpbSearchDoc, 1, :default => []
208
+ optional :max_score, :float, 2
209
+ optional :num_found, :uint32, 3
210
+ end
163
211
  end
164
212
  end
165
213
  end
@@ -31,40 +31,33 @@ module Riak
31
31
  return robject if pbuf.respond_to?(:unchanged) && pbuf.unchanged # Reloading
32
32
  robject.vclock = Base64.encode64(pbuf.vclock).chomp if pbuf.vclock
33
33
  robject.key = maybe_unescape(pbuf.key) if pbuf.respond_to?(:key) && pbuf.key # Put w/o key
34
- if pbuf.content.size > 1
35
- robject.conflict = true
36
- robject.siblings = pbuf.content.map do |c|
37
- sibling = RObject.new(robject.bucket, robject.key)
38
- sibling.vclock = robject.vclock
34
+ robject.siblings = pbuf.content.map do |c|
35
+ RContent.new(robject) do |sibling|
39
36
  load_content(c, sibling)
40
37
  end
41
-
42
- return robject.attempt_conflict_resolution
43
- else
44
- load_content(pbuf.content.first, robject)
45
38
  end
46
- robject
39
+ robject.conflict? ? robject.attempt_conflict_resolution : robject
47
40
  end
48
41
 
49
42
  private
50
- def load_content(pbuf, robject)
43
+ def load_content(pbuf, rcontent)
51
44
  if ENCODING && pbuf.charset.present?
52
45
  pbuf.value.force_encoding(pbuf.charset) if Encoding.find(pbuf.charset)
53
46
  end
54
- robject.raw_data = pbuf.value
55
- robject.etag = pbuf.vtag if pbuf.vtag.present?
56
- robject.content_type = pbuf.content_type if pbuf.content_type.present?
57
- robject.links = pbuf.links.map(&method(:decode_link)) if pbuf.links.present?
58
- pbuf.usermeta.each {|pair| decode_meta(pair, robject.meta) } if pbuf.usermeta.present?
47
+ rcontent.raw_data = pbuf.value
48
+ rcontent.etag = pbuf.vtag if pbuf.vtag.present?
49
+ rcontent.content_type = pbuf.content_type if pbuf.content_type.present?
50
+ rcontent.links = pbuf.links.map(&method(:decode_link)) if pbuf.links.present?
51
+ pbuf.usermeta.each {|pair| decode_meta(pair, rcontent.meta) } if pbuf.usermeta.present?
59
52
  if pbuf.indexes.present?
60
- robject.indexes.clear
61
- pbuf.indexes.each {|pair| decode_index(pair, robject.indexes) }
53
+ rcontent.indexes.clear
54
+ pbuf.indexes.each {|pair| decode_index(pair, rcontent.indexes) }
62
55
  end
63
56
  if pbuf.last_mod.present?
64
- robject.last_modified = Time.at(pbuf.last_mod)
65
- robject.last_modified += pbuf.last_mod_usecs / 1000000 if pbuf.last_mod_usecs.present?
57
+ rcontent.last_modified = Time.at(pbuf.last_mod)
58
+ rcontent.last_modified += pbuf.last_mod_usecs / 1000000 if pbuf.last_mod_usecs.present?
66
59
  end
67
- robject
60
+ rcontent
68
61
  end
69
62
 
70
63
  def decode_link(pbuf)
@@ -31,7 +31,7 @@ module Riak
31
31
  end
32
32
 
33
33
  def fetch_object(bucket, key, options={})
34
- options = normalize_quorums(options)
34
+ options = prune_unsupported_options(:GetReq, normalize_quorums(options))
35
35
  bucket = Bucket === bucket ? bucket.name : bucket
36
36
  req = RpbGetReq.new(options.merge(:bucket => maybe_encode(bucket), :key => maybe_encode(key)))
37
37
  write_protobuff(:GetReq, req)
@@ -43,18 +43,25 @@ module Riak
43
43
  options[:bucket] = maybe_encode(robject.bucket.name)
44
44
  options[:key] = maybe_encode(robject.key)
45
45
  options[:if_modified] = maybe_encode Base64.decode64(robject.vclock) if robject.vclock
46
- req = RpbGetReq.new(options)
46
+ req = RpbGetReq.new(prune_unsupported_options(:GetReq, options))
47
47
  write_protobuff(:GetReq, req)
48
48
  decode_response(robject)
49
49
  end
50
50
 
51
51
  def store_object(robject, options={})
52
+ options = normalize_quorums(options)
52
53
  if robject.prevent_stale_writes
53
- other = fetch_object(robject.bucket, robject.key)
54
- raise Riak::ProtobuffsFailedRequest(:stale_object, t("stale_write_prevented")) unless other.vclock == robject.vclock
54
+ unless pb_conditionals?
55
+ other = fetch_object(robject.bucket, robject.key)
56
+ raise Riak::ProtobuffsFailedRequest.new(:stale_object, t("stale_write_prevented")) unless other.vclock == robject.vclock
57
+ end
58
+ if robject.vclock
59
+ options[:if_not_modified] = true
60
+ else
61
+ options[:if_none_match] = true
62
+ end
55
63
  end
56
- options = normalize_quorums(options)
57
- req = dump_object(robject, options)
64
+ req = dump_object(robject, prune_unsupported_options(:PutReq, options))
58
65
  write_protobuff(:PutReq, req)
59
66
  decode_response(robject)
60
67
  end
@@ -65,7 +72,7 @@ module Riak
65
72
  options[:bucket] = maybe_encode(bucket)
66
73
  options[:key] = maybe_encode(key)
67
74
  options[:vclock] = Base64.decode64(options[:vclock]) if options[:vclock]
68
- req = RpbDelReq.new(options)
75
+ req = RpbDelReq.new(prune_unsupported_options(:DelReq, options))
69
76
  write_protobuff(:DelReq, req)
70
77
  decode_response
71
78
  end
@@ -102,19 +109,47 @@ module Riak
102
109
  end
103
110
 
104
111
  def mapred(mr, &block)
112
+ raise MapReduceError.new(t("empty_map_reduce_query")) if mr.query.empty? && !mapred_phaseless?
105
113
  req = RpbMapRedReq.new(:request => mr.to_json, :content_type => "application/json")
106
114
  write_protobuff(:MapRedReq, req)
107
- results = []
115
+ results = MapReduce::Results.new(mr)
108
116
  while msg = decode_response
109
117
  break if msg.done
110
118
  if block_given?
111
119
  yield msg.phase, JSON.parse(msg.response)
112
120
  else
113
- results[msg.phase] ||= []
114
- results[msg.phase] += JSON.parse(msg.response)
121
+ results.add msg.phase, JSON.parse(msg.response)
115
122
  end
116
123
  end
117
- block_given? || results.compact.size == 1 ? results.last : results
124
+ block_given? || results.report
125
+ end
126
+
127
+ def get_index(bucket, index, query)
128
+ return super unless pb_indexes?
129
+ if Range === query
130
+ options = {
131
+ :qtype => RpbIndexReq::IndexQueryType::RANGE,
132
+ :range_min => query.begin.to_s,
133
+ :range_max => query.end.to_s
134
+ }
135
+ else
136
+ options = {
137
+ :qtype => RpbIndexReq::IndexQueryType::EQ,
138
+ :key => query.to_s
139
+ }
140
+ end
141
+ req = RpbIndexReq.new(options.merge(:bucket => bucket, :index => index))
142
+ write_protobuff(:IndexReq, req)
143
+ decode_response
144
+ end
145
+
146
+ def search(index, query, options={})
147
+ return super unless pb_search?
148
+ options = options.symbolize_keys
149
+ options[:op] = options.delete(:'q.op') if options[:'q.op']
150
+ req = RpbSearchQueryReq.new(options.merge(:index => index || 'search', :q => query))
151
+ write_protobuff(:SearchQueryReq, req)
152
+ decode_response
118
153
  end
119
154
 
120
155
  private
@@ -167,12 +202,23 @@ module Riak
167
202
  {'n_val' => res.props.n_val, 'allow_mult' => res.props.allow_mult}
168
203
  when :MapRedResp
169
204
  RpbMapRedResp.decode(message)
205
+ when :IndexResp
206
+ res = RpbIndexResp.decode(message)
207
+ res.keys
208
+ when :SearchQueryResp
209
+ res = RpbSearchQueryResp.decode(message)
210
+ { 'docs' => res.docs.map {|d| decode_doc(d) },
211
+ 'max_score' => res.max_score,
212
+ 'num_found' => res.num_found }
170
213
  end
171
214
  end
172
215
  rescue SystemCallError, SocketError => e
173
216
  reset_socket
174
217
  raise
175
- #raise Riak::ProtobuffsFailedRequest.new(:server_error, e.message)
218
+ end
219
+
220
+ def decode_doc(doc)
221
+ Hash[doc.properties.map {|p| [ p.key, p.value ] }]
176
222
  end
177
223
  end
178
224
  end
@@ -1,28 +1,36 @@
1
- class Riak::Client::Decaying
2
- # A float which decays exponentially with time.
1
+ module Riak
2
+ class Client
3
+ # A float value which decays exponentially toward 0 over time.
4
+ # @private
5
+ class Decaying
6
+ attr_accessor :e
7
+ attr_accessor :p
3
8
 
4
- attr_accessor :e
5
- attr_accessor :p
6
- # @param[:p] The initial value
7
- # @param[:e] Exponent base
8
- # @param[:r] Timescale
9
- def initialize(opts = {})
10
- @p = opts[:p] || 0
11
- @e = opts[:e] || Math::E
12
- @r = opts[:r] || Math.log(0.5) / 10
13
- @t0 = Time.now
14
- end
9
+ # @param [Hash] opts options
10
+ # @option options [Float] :p (0.0) The initial value
11
+ # @option options [Float] :e (Math::E) Exponent base
12
+ # @option options [Float] :r (Math.log(0.5) / 10) Timescale
13
+ # factor - defaulting to decay 50% every 10 seconds
14
+ def initialize(opts = {})
15
+ @p = opts[:p] || 0.0
16
+ @e = opts[:e] || Math::E
17
+ @r = opts[:r] || Math.log(0.5) / 10
18
+ @t0 = Time.now
19
+ end
15
20
 
16
- # Add d to current value.
17
- def <<(d)
18
- @p = value + d
19
- end
21
+ # Add to current value.
22
+ # @param [Float] d the value to add
23
+ def <<(d)
24
+ @p = value + d
25
+ end
20
26
 
21
- # Return current value
22
- def value
23
- now = Time.now
24
- dt = now - @t0
25
- @t0 = now
26
- @p = @p * (@e ** (@r * dt))
27
+ # @return [Float] the current value (adjusted for the time decay)
28
+ def value
29
+ now = Time.now
30
+ dt = now - @t0
31
+ @t0 = now
32
+ @p = @p * (@e ** (@r * dt))
33
+ end
34
+ end
27
35
  end
28
36
  end