risky 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 365f66d97a0adebc22c466d92764fd1a7f16bef2
4
+ data.tar.gz: e57eb61d6dcb1a58b4899f123388acab66c9b7da
5
+ SHA512:
6
+ metadata.gz: 3686bd912cab327d870870ec35661259e6360aa6e1b3c87ac44ab256c2ca22b651edb768394a9236160344c557ae83c4555bf79b2efbfb8e38c6067c8056d042
7
+ data.tar.gz: 7f948333bb7d410c949d6ec40dd8a0875e440e67dedcc2274b256dffe5479b06a2342d68812f41b7cccffdb8eebf321fb8a79a426b0e597535a8af34c5e4020e
@@ -0,0 +1,8 @@
1
+ pkg/
2
+ ._*
3
+ *~
4
+ .DS_Store
5
+ .*.swp
6
+ .rvmrc
7
+ .ruby-version
8
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ risky (1.1.0)
5
+ riak-client (~> 1.4.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ beefcake (0.3.7)
11
+ builder (3.2.2)
12
+ diff-lcs (1.2.4)
13
+ excon (0.25.3)
14
+ i18n (0.6.5)
15
+ innertube (1.0.2)
16
+ multi_json (1.7.9)
17
+ riak-client (1.4.0)
18
+ beefcake (~> 0.3.7)
19
+ builder (>= 2.1.2)
20
+ i18n (>= 0.4.0)
21
+ innertube (~> 1.0.2)
22
+ multi_json (~> 1.0)
23
+ rspec (2.14.1)
24
+ rspec-core (~> 2.14.0)
25
+ rspec-expectations (~> 2.14.0)
26
+ rspec-mocks (~> 2.14.0)
27
+ rspec-core (2.14.5)
28
+ rspec-expectations (2.14.2)
29
+ diff-lcs (>= 1.1.3, < 2.0)
30
+ rspec-mocks (2.14.3)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ excon
37
+ risky!
38
+ rspec
@@ -1,8 +1,6 @@
1
1
  Risky
2
2
  =====
3
3
 
4
- (Hey guys, I'll be porting over tests from our internal suite in the next few evenings. --Kyle)
5
-
6
4
  A simple, lightweight object layer for Riak.
7
5
 
8
6
  $ gem install risky
@@ -14,11 +12,11 @@ A simple, lightweight object layer for Riak.
14
12
  User.new('clu', 'fights' => 'for the users').save
15
13
  User['clu']['fights'] #=> 'for the users'
16
14
 
17
- Built on top of seancribb's excellent riak-client, Risky provides basic
15
+ Built on top of basho's excellent riak-client, Risky provides basic
18
16
  infrastructure for designing models with attributes (including defaults and
19
17
  casting to/from JSON), conflict resolution, validation, lifecycle callbacks,
20
18
  link-walking, mapreduce, and more. Modules are available for timestamps,
21
- chronologically ordered lists, and basic secondary indexes.
19
+ chronologically ordered lists, and secondary indexes (2i).
22
20
 
23
21
  Risky does not provide the rich API of Ripple, but it also does not require
24
22
  activesupport. It strives to be understandable, minimal, and modular. Magic is
@@ -56,6 +54,12 @@ Show me the code!
56
54
  links :followers
57
55
  end
58
56
 
57
+ Contributors
58
+ ------------
59
+
60
+ Dalibor Nasevic ([@dalibor](https://github.com/dalibor))
61
+ Marc Heiligers ([@marcheiligers](https://github.com/marcheiligers))
62
+
59
63
  License
60
64
  -------
61
65
 
@@ -0,0 +1,31 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ namespace :test do
4
+ desc "Delete test buckets"
5
+ task :delete_buckets do
6
+ require 'riak'
7
+ Riak.disable_list_keys_warnings = true
8
+ client = Riak::Client.new(:host => '127.0.0.1', :protocol => 'pbc')
9
+ bucket_names = [
10
+ 'risky_enum',
11
+ 'risky_indexes',
12
+ 'risky_crud',
13
+ 'risky_items',
14
+ 'risky_users',
15
+ 'risky_mult',
16
+ 'risky_concurrent',
17
+ 'risky_cron_list',
18
+ 'risky_albums',
19
+ 'risky_artists',
20
+ 'risky_labels',
21
+ 'risky_cities',
22
+ 'risky_indexes_by_unique',
23
+ 'risky_indexes_by_value'
24
+ ]
25
+ bucket_names.each do |bucket_name|
26
+ bucket = client.bucket(bucket_name)
27
+ puts "Deleting keys in #{bucket_name}"
28
+ bucket.keys.map { |k| bucket.delete(k) }
29
+ end
30
+ end
31
+ end
@@ -1,302 +1,279 @@
1
+ require 'set'
2
+ require 'riak'
3
+ require 'multi_json'
4
+
1
5
  class Risky
2
- require 'set'
3
- require 'riak'
4
- require 'multi_json'
6
+ autoload :Invalid, 'risky/invalid'
7
+ autoload :NotFound, 'risky/not_found'
8
+ autoload :ListKeys, 'risky/list_keys'
9
+ autoload :CronList, 'risky/cron_list'
10
+ autoload :Indexes, 'risky/indexes'
11
+ autoload :SecondaryIndexes, 'risky/secondary_indexes'
12
+ autoload :Timestamps, 'risky/timestamps'
13
+ autoload :Inflector, 'risky/inflector'
14
+ autoload :PaginatedCollection, 'risky/paginated_collection'
15
+ autoload :GZip, 'risky/gzip'
5
16
 
6
- $LOAD_PATH << File.expand_path(File.dirname(__FILE__))
17
+ extend Enumerable
7
18
 
8
- # Exceptions
9
- require 'risky/invalid'
10
- require 'risky/not_found'
19
+ class << self
20
+ # Get a model by key. Returns nil if not found. You can also pass opts to
21
+ # #reload (e.g. :r, :merge => false).
22
+ def [](key, opts = {})
23
+ return nil unless key
11
24
 
12
- # Fix threading autoload bugs
13
- require 'risky/threadsafe'
14
-
15
- # Default plugins
16
- require 'risky/cron_list'
17
- require 'risky/indexes'
18
- require 'risky/timestamps'
25
+ begin
26
+ new(key).reload(opts)
27
+ rescue Riak::FailedRequest => e
28
+ raise e unless e.not_found?
29
+ nil
30
+ end
31
+ end
19
32
 
20
- extend Enumerable
33
+ def find(key, opts = {})
34
+ self[key.to_s, opts]
35
+ end
21
36
 
22
- # Get a model by key. Returns nil if not found. You can also pass opts to
23
- # #reload (e.g. :r, :merge => false).
24
- def self.[](key, opts = {})
25
- return nil unless key
37
+ def find_all_by_key(keys)
38
+ return [] if keys.blank?
26
39
 
27
- begin
28
- new(key).reload(opts)
29
- rescue Riak::FailedRequest => e
30
- raise e unless e.not_found?
31
- nil
40
+ keys.map!(&:to_s)
41
+ results = bucket.get_many(keys)
42
+ keys.map { |key| from_riak_object(results[key]) }.compact
32
43
  end
33
- end
34
44
 
35
- # Returns all model instances from the bucket. Why yes, this *could* be
36
- # expensive, Suzy!
37
- def self.all(opts = {:reload => true})
38
- bucket.keys(opts).map do |key|
39
- self[key]
45
+ def create(key = nil, values = {}, opts = {})
46
+ new(key, values).save(opts)
40
47
  end
41
- end
42
48
 
43
- # Indicates that this model may be multivalued; in which case .merge should
44
- # also be defined.
45
- def self.allow_mult
46
- unless bucket.props['allow_mult']
47
- bucket.props = bucket.props.merge('allow_mult' => true)
49
+ # Indicates that this model may be multivalued; in which case .merge should
50
+ # also be defined.
51
+ def allow_mult
52
+ unless bucket.props['allow_mult']
53
+ bucket.props = bucket.props.merge('allow_mult' => true)
54
+ end
48
55
  end
49
- end
50
56
 
51
- # The Riak::Bucket backing this model.
52
- # If name is passed, *sets* the bucket name.
53
- def self.bucket(name = nil)
54
- if name
55
- @bucket_name = name.to_s
57
+ # The Riak::Bucket backing this model.
58
+ # If name is passed, *sets* the bucket name.
59
+ def bucket(name = nil)
60
+ if name
61
+ @bucket_name = name.to_s
62
+ end
63
+
64
+ riak.bucket(@bucket_name)
56
65
  end
57
-
58
- riak.bucket(@bucket_name)
59
- end
60
66
 
61
- # The string name of the bucket used for storing instances of this model.
62
- def self.bucket_name
63
- @bucket_name
64
- end
67
+ # The string name of the bucket used for storing instances of this model.
68
+ def bucket_name
69
+ @bucket_name
70
+ end
65
71
 
66
- def self.bucket_name=(bucket)
67
- @bucket_name = name.to_s
68
- end
72
+ def bucket_name=(bucket)
73
+ @bucket_name = name.to_s
74
+ end
69
75
 
70
- # Casts data to appropriate types for values.
71
- def self.cast(data)
72
- casted = {}
73
- data.each do |k, v|
74
- c = @values[k][:class] rescue nil
75
- casted[k] = begin
76
- if c == Time
77
- Time.iso8601(v)
78
- else
76
+ def content_type
77
+ "application/json"
78
+ end
79
+
80
+ # Casts data to appropriate types for values.
81
+ def cast(data)
82
+ casted = {}
83
+ data.each do |k, v|
84
+ c = @values[k][:class] rescue nil
85
+ casted[k] = begin
86
+ if c == Time
87
+ Time.iso8601(v)
88
+ else
89
+ v
90
+ end
91
+ rescue
79
92
  v
80
93
  end
81
- rescue
82
- v
83
94
  end
95
+ casted
84
96
  end
85
- casted
86
- end
87
97
 
88
- # Counts the number of values in the bucket via key streaming
89
- def self.count
90
- count = 0
91
- bucket.keys do |keys|
92
- count += keys.length
98
+ # Returns true when record deleted.
99
+ # Returns nil when record was not present to begin with.
100
+ def delete(key, opts = {})
101
+ return if key.nil?
102
+ bucket.delete(key.to_s, opts)
93
103
  end
94
- count
95
- end
96
104
 
97
- # Returns true when record deleted.
98
- # Returns nil when record was not present to begin with.
99
- def self.delete(key, opts = {})
100
- return if key.nil?
101
- (bucket.delete(key.to_s, opts)[:code] == 204) or nil
102
- end
103
-
104
- # Iterate over all items using key streaming.
105
- def self.each
106
- bucket.keys do |keys|
107
- keys.each do |key|
108
- if x = self[key]
109
- yield x
110
- end
111
- end
105
+ # Does the given key exist in our bucket?
106
+ def exists?(key)
107
+ return if key.nil?
108
+ bucket.exists? key.to_s
112
109
  end
113
- end
114
110
 
115
- # Does the given key exist in our bucket?
116
- def self.exists?(key)
117
- return if key.nil?
118
- bucket.exists? key.to_s
119
- end
111
+ # Fills in values from a Riak::RObject
112
+ def from_riak_object(riak_object)
113
+ return nil if riak_object.nil?
120
114
 
121
- # Fills in values from a Riak::RObject
122
- def self.from_riak_object(riak_object)
123
- return nil if riak_object.nil?
115
+ n = new.load_riak_object riak_object
124
116
 
125
- n = new.load_riak_object riak_object
117
+ # Callback
118
+ n.after_load
119
+ n
120
+ end
126
121
 
127
- # Callback
128
- n.after_load
129
- n
130
- end
131
-
132
- # Gets an existing record or creates one.
133
- def self.get_or_new(*args)
134
- self[*args] or new(args.first)
135
- end
136
-
137
- # Iterate over all keys.
138
- def self.keys(*a)
139
- if block_given?
140
- bucket.keys(*a) do |keys|
141
- # This API is currently inconsistent from protobuffs to http
142
- if keys.kind_of? Array
143
- keys.each do |key|
144
- yield key
122
+ # Gets an existing record or creates one.
123
+ def get_or_new(*args)
124
+ self[*args] or new(args.first)
125
+ end
126
+
127
+ # Establishes methods for manipulating a single link with a given tag.
128
+ def link(tag)
129
+ tag = tag.to_s
130
+ class_eval %Q{
131
+ def #{tag}
132
+ begin
133
+ @riak_object.links.find do |l|
134
+ l.tag == #{tag.inspect}
135
+ end.key
136
+ rescue NoMethodError
137
+ nil
145
138
  end
146
- else
147
- yield keys
148
139
  end
149
- end
150
- else
151
- bucket.keys(*a)
152
- end
153
- end
154
140
 
155
- # Establishes methods for manipulating a single link with a given tag.
156
- def self.link(tag)
157
- tag = tag.to_s
158
- class_eval "
159
- def #{tag}
160
- begin
161
- @riak_object.links.find do |l|
141
+ def #{tag}=(link)
142
+ @riak_object.links.reject! do |l|
162
143
  l.tag == #{tag.inspect}
163
- end.key
164
- rescue NoMethodError
165
- nil
144
+ end
145
+ if link
146
+ @riak_object.links << link.to_link(#{tag.inspect})
147
+ end
166
148
  end
167
- end
149
+ }
150
+ end
168
151
 
169
- def #{tag}=(link)
170
- @riak_object.links.reject! do |l|
171
- l.tag == #{tag.inspect}
152
+ # Establishes methods for manipulating a set of links with a given tag.
153
+ def links(tag)
154
+ tag = tag.to_s
155
+ class_eval %Q{
156
+ def #{tag}
157
+ @riak_object.links.select do |l|
158
+ l.tag == #{tag.inspect}
159
+ end.map do |l|
160
+ l.key
161
+ end
172
162
  end
173
- if link
163
+
164
+ def add_#{tag}(link)
174
165
  @riak_object.links << link.to_link(#{tag.inspect})
175
166
  end
176
- end
177
- "
178
- end
179
-
180
- # Establishes methods for manipulating a set of links with a given tag.
181
- def self.links(tag)
182
- tag = tag.to_s
183
- class_eval "
184
- def #{tag}
185
- @riak_object.links.select do |l|
186
- l.tag == #{tag.inspect}
187
- end.map do |l|
188
- l.key
167
+
168
+ def remove_#{tag}(link)
169
+ @riak_object.links.delete link.to_link(#{tag.inspect})
189
170
  end
190
- end
191
171
 
192
- def add_#{tag}(link)
193
- @riak_object.links << link.to_link(#{tag.inspect})
194
- end
172
+ def clear_#{tag}
173
+ @riak_object.links.delete_if do |l|
174
+ l.tag == #{tag.inspect}
175
+ end
176
+ end
195
177
 
196
- def remove_#{tag}(link)
197
- @riak_object.links.delete link.to_link(#{tag.inspect})
198
- end
178
+ def #{tag}_count
179
+ @riak_object.links.select{|l| l.tag == #{tag.inspect}}.length
180
+ end
181
+ }
182
+ end
199
183
 
200
- def clear_#{tag}
201
- @riak_object.links.delete_if do |l|
202
- l.tag == #{tag.inspect}
184
+ # Mapreduce helper
185
+ def map(*args)
186
+ mr.map(*args)
187
+ end
188
+
189
+ # Merges n versions of a record together, for read-repair.
190
+ # Returns the merged record.
191
+ def merge(versions)
192
+ versions.first
193
+ end
194
+
195
+ # Begins a mapreduce on this model's bucket.
196
+ # If no keys are given, operates on the entire bucket.
197
+ # If keys are given, operates on those keys first.
198
+ def mr(keys = nil)
199
+ mr = Riak::MapReduce.new(riak)
200
+
201
+ if keys
202
+ # Add specific keys
203
+ [*keys].compact.inject(mr) do |mr, key|
204
+ mr.add @bucket_name, key.to_s
203
205
  end
206
+ else
207
+ # Add whole bucket
208
+ mr.add @bucket_name
204
209
  end
205
-
206
- def #{tag}_count
207
- @riak_object.links.select{|l| l.tag == #{tag.inspect}}.length
208
- end
209
- "
210
- end
210
+ end
211
211
 
212
- # Mapreduce helper
213
- def self.map(*args)
214
- mr.map(*args)
215
- end
212
+ # MR helper.
213
+ def reduce(*args)
214
+ mr.reduce(*args)
215
+ end
216
216
 
217
- # Merges n versions of a record together, for read-repair.
218
- # Returns the merged record.
219
- def self.merge(versions)
220
- versions.first
221
- end
217
+ # The Riak::Client backing this model class.
218
+ def riak
219
+ Thread.current[:riak_client] ||=
220
+ if @riak and @riak.respond_to?(:call)
221
+ @riak.call(self)
222
+ elsif @riak
223
+ @riak
224
+ else
225
+ superclass.riak
226
+ end
227
+ end
222
228
 
223
- # Begins a mapreduce on this model's bucket.
224
- # If no keys are given, operates on the entire bucket.
225
- # If keys are given, operates on those keys first.
226
- def self.mr(keys = nil)
227
- mr = Riak::MapReduce.new(riak)
229
+ # Forces Riak client for this thread to be reset.
230
+ # If your @riak proc can choose between multiple hosts, calling this on
231
+ # failure will allow subsequent requests to proceed on another host.
232
+ def riak!
233
+ Thread.current[:riak_client] = nil
234
+ riak
235
+ end
228
236
 
229
- if keys
230
- # Add specific keys
231
- [*keys].compact.inject(mr) do |mr, key|
232
- mr.add @bucket_name, key.to_s
233
- end
234
- else
235
- # Add whole bucket
236
- mr.add @bucket_name
237
+ # Sets the Riak Client backing this model class. If client is a lambda (or
238
+ # anything responding to #call), it will be invoked to generate a new client
239
+ # every time Risky feels it is appropriate.
240
+ def riak=(client)
241
+ @riak = client
237
242
  end
238
- end
239
243
 
240
- # MR helper.
241
- def self.reduce(*args)
242
- mr.reduce(*args)
243
- end
244
+ # Add a new value to this model. Values aren't necessary; you can
245
+ # use Risky#[], but if you would like to cast values to/from JSON or
246
+ # specify defaults, you may:
247
+ #
248
+ # :default => object (#clone is called for each new instance)
249
+ # :class => Time, Integer, etc. Inferred from default.class if present.
250
+ def value(value, opts = {})
251
+ value = value.to_s
252
+
253
+ klass = if opts[:class]
254
+ opts[:class]
255
+ elsif opts.include? :default
256
+ opts[:default].class
257
+ else
258
+ nil
259
+ end
260
+ values[value] = opts.merge(:class => klass)
244
261
 
245
- # The Riak::Client backing this model class.
246
- def self.riak
247
- if @riak_client
248
- @riak_client
249
- elsif @riak and @riak.respond_to? :call
250
- @riak_client = @riak.call(self)
251
- elsif @riak
252
- @riak_client = @riak
253
- else
254
- superclass.riak
255
- end
256
- end
257
-
258
- # Forces this model's Riak client to be reset.
259
- # If your @riak proc can choose between multiple hosts, calling this on
260
- # failure will allow subsequent requests to proceed on another host.
261
- def self.riak!
262
- @riak_client = nil
263
- riak
264
- end
265
-
266
- # Sets the Riak Client backing this model class. If client is a lambda (or
267
- # anything responding to #call), it will be invoked to generate a new client
268
- # every time Risky feels it is appropriate.
269
- def self.riak=(client)
270
- @riak = client
271
- end
272
-
273
- # Add a new value to this model. Values aren't necessary; you can
274
- # use Risky#[], but if you would like to cast values to/from JSON or
275
- # specify defaults, you may:
276
- #
277
- # :default => object (#clone is called for each new instance)
278
- # :class => Time, Integer, etc. Inferred from default.class if present.
279
- def self.value(value, opts = {})
280
- value = value.to_s
281
-
282
- klass = if opts[:class]
283
- opts[:class]
284
- elsif opts.include? :default
285
- opts[:default].class
286
- else
287
- nil
262
+ class_eval %Q{
263
+ def #{value}
264
+ @values[#{value.inspect}]
265
+ end
266
+
267
+ def #{value}=(value)
268
+ @values[#{value.inspect}] = value
269
+ end
270
+ }
288
271
  end
289
- values[value] = opts.merge(:class => klass)
290
-
291
- class_eval "
292
- def #{value}; @values[#{value.inspect}]; end
293
- def #{value}=(value); @values[#{value.inspect}] = value; end
294
- "
295
- end
296
272
 
297
- # A list of all values we track.
298
- def self.values
299
- @values ||= {}
273
+ # A list of all values we track.
274
+ def values
275
+ @values ||= {}
276
+ end
300
277
  end
301
278
 
302
279
 
@@ -317,7 +294,7 @@ class Risky
317
294
  key = key.to_s unless key.nil?
318
295
 
319
296
  @riak_object ||= Riak::RObject.new(self.class.bucket, key)
320
- @riak_object.content_type = 'application/json'
297
+ @riak_object.content_type = self.class.content_type
321
298
 
322
299
  @new = true
323
300
  @merged = false
@@ -337,22 +314,27 @@ class Risky
337
314
  if self[k].nil?
338
315
  self[k] = (v[:default].clone rescue v[:default])
339
316
  end
340
- end
317
+ end
318
+ end
319
+
320
+ def ==(object)
321
+ object.class == self.class &&
322
+ (object.key.present? && object.key == self.key || object.object_id == self.object_id)
341
323
  end
324
+ alias :eql? :==
342
325
 
343
- # Two models compare === if they are of matching class and key.
344
- def ===(o)
345
- o.class == self.class and o.key.to_s == self.key.to_s rescue false
326
+ def ===(object)
327
+ object.is_a?(self.class)
346
328
  end
347
329
 
348
330
  # Access the values hash.
349
331
  def [](k)
350
- @values[k]
332
+ values[k]
351
333
  end
352
334
 
353
335
  # Access the values hash.
354
336
  def []=(k, v)
355
- @values[k] = v
337
+ values[k] = v
356
338
  end
357
339
 
358
340
  def after_create
@@ -371,7 +353,7 @@ class Risky
371
353
  def as_json(opts = {})
372
354
  h = @values.merge(:key => key)
373
355
  h[:errors] = errors unless errors.empty?
374
- h
356
+ h
375
357
  end
376
358
 
377
359
  # Called before creation and validation
@@ -406,7 +388,12 @@ class Risky
406
388
  # Engage conflict resolution mode
407
389
  final = self.class.merge(
408
390
  siblings.map do |sibling|
409
- self.class.new.load_riak_object(sibling, :merge => false)
391
+ robject = Riak::RObject.new(sibling.bucket, sibling.key)
392
+ robject.raw_data = sibling.raw_data
393
+ robject.content_type = sibling.content_type
394
+ # robject.siblings = [sibling]
395
+ robject.vclock = sibling.vclock
396
+ self.class.new.load_riak_object(robject, :merge => false)
410
397
  end
411
398
  )
412
399
 
@@ -418,7 +405,7 @@ class Risky
418
405
  self.merged = true
419
406
  else
420
407
  # Not merging
421
- self.values = self.class.cast(MultiJson.load(riak_object.raw_data)) rescue {}
408
+ self.values = self.class.cast(riak_object.data) rescue {}
422
409
  self.class.values.each do |k, v|
423
410
  if values[k].nil?
424
411
  values[k] = (v[:default].clone rescue v[:default])
@@ -428,7 +415,7 @@ class Risky
428
415
  self.new = false
429
416
  self.merged = false
430
417
  end
431
-
418
+
432
419
  self
433
420
  end
434
421
 
@@ -441,13 +428,23 @@ class Risky
441
428
  @riak_object.key = nil
442
429
  else
443
430
  @riak_object.key = key.to_s
444
- end
431
+ end
445
432
  end
446
433
 
447
434
  def key
448
435
  @riak_object.key
449
436
  end
450
437
 
438
+ def id
439
+ Integer(key)
440
+ rescue ArgumentError
441
+ key
442
+ end
443
+
444
+ def id=(value)
445
+ self.key = value
446
+ end
447
+
451
448
  def merged=(merged)
452
449
  @merged = !!merged
453
450
  end
@@ -481,7 +478,7 @@ class Risky
481
478
  end
482
479
 
483
480
  # Saves this model.
484
- #
481
+ #
485
482
  # Calls #validate and #valid? unless :validate is false.
486
483
  #
487
484
  # Converts @values to_json and saves it to riak.
@@ -495,9 +492,9 @@ class Risky
495
492
  return false unless valid?
496
493
  end
497
494
 
498
- @riak_object.raw_data = MultiJson.dump @values
499
- @riak_object.content_type = "application/json"
500
-
495
+ @riak_object.data = @values
496
+ @riak_object.content_type = self.class.content_type
497
+
501
498
  store_opts = {}
502
499
  store_opts[:w] = opts[:w] if opts[:w]
503
500
  store_opts[:dw] = opts[:dw] if opts[:dw]
@@ -511,6 +508,18 @@ class Risky
511
508
  self
512
509
  end
513
510
 
511
+ def update_attribute(attribute, value)
512
+ self.send("#{attribute}=", value)
513
+ self.save
514
+ end
515
+
516
+ def update_attributes(attributes)
517
+ attributes.each do |attribute, value|
518
+ self.send("#{attribute}=", value)
519
+ end
520
+ self.save
521
+ end
522
+
514
523
  # This is provided for convenience; #save does *not* use this method, and you
515
524
  # are free to override it.
516
525
  def to_json(*a)
@@ -527,10 +536,10 @@ class Risky
527
536
  @errors = {}
528
537
  validate
529
538
  @errors.empty?
530
- end
531
-
539
+ end
540
+
532
541
  # Determines whether the model is valid. Sets the contents of #errors if
533
- # invalid.
542
+ # invalid.
534
543
  def validate
535
544
  if key.blank?
536
545
  errors[:key] = 'is missing'