Class: Risky

Inherits:
Object
  • Object
show all
Extended by:
Enumerable
Defined in:
lib/risky.rb,
lib/risky/version.rb

Defined Under Namespace

Modules: CronList, GZip, Indexes, Inflector, ListKeys, Resolver, SecondaryIndexes, Timestamps Classes: Invalid, NotFound, PaginatedCollection

Constant Summary collapse

VERSION =
"1.1.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key = nil, values = {}) ⇒ Risky

Create a new instance from a key and a list of values.

Values will be passed to attr= methods where possible, so you can write def password=(p)

self['password'] = md5sum p

end User.new(‘me’, :password => ‘baggins’)



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/risky.rb', line 291

def initialize(key = nil, values = {})
  super()

  key = key.to_s unless key.nil?

  @riak_object ||= Riak::RObject.new(self.class.bucket, key)
  @riak_object.content_type = self.class.content_type

  @new = true
  @merged = false
  @values = {}

  # Load values
  values.each do |k,v|
    begin
      send(k.to_s + '=', v)
    rescue NoMethodError
      self[k] = v
    end
  end

  # Fill in defults.
  self.class.values.each do |k,v|
    if self[k].nil?
      self[k] = (v[:default].clone rescue v[:default])
    end
  end
end

Instance Attribute Details

#riak_objectObject

Returns the value of attribute riak_object.



282
283
284
# File 'lib/risky.rb', line 282

def riak_object
  @riak_object
end

#valuesObject

Returns the value of attribute values.



281
282
283
# File 'lib/risky.rb', line 281

def values
  @values
end

Class Method Details

.[](key, opts = {}) ⇒ Object

Get a model by key. Returns nil if not found. You can also pass opts to #reload (e.g. :r, :merge => false).



22
23
24
25
26
27
28
29
30
31
# File 'lib/risky.rb', line 22

def [](key, opts = {})
  return nil unless key

  begin
    new(key).reload(opts)
  rescue Riak::FailedRequest => e
    raise e unless e.not_found?
    nil
  end
end

.allow_multObject

Indicates that this model may be multivalued; in which case .merge should also be defined.



51
52
53
54
55
# File 'lib/risky.rb', line 51

def allow_mult
  unless bucket.props['allow_mult']
    bucket.props = bucket.props.merge('allow_mult' => true)
  end
end

.bucket(name = nil) ⇒ Object

The Riak::Bucket backing this model. If name is passed, sets the bucket name.



59
60
61
62
63
64
65
# File 'lib/risky.rb', line 59

def bucket(name = nil)
  if name
    @bucket_name = name.to_s
  end

  riak.bucket(@bucket_name)
end

.bucket_nameObject

The string name of the bucket used for storing instances of this model.



68
69
70
# File 'lib/risky.rb', line 68

def bucket_name
  @bucket_name
end

.bucket_name=(bucket) ⇒ Object



72
73
74
# File 'lib/risky.rb', line 72

def bucket_name=(bucket)
  @bucket_name = name.to_s
end

.cast(data) ⇒ Object

Casts data to appropriate types for values.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/risky.rb', line 81

def cast(data)
  casted = {}
  data.each do |k, v|
    c = @values[k][:class] rescue nil
    casted[k] = begin
      if c == Time
        Time.iso8601(v)
      else
        v
      end
    rescue
      v
    end
  end
  casted
end

.content_typeObject



76
77
78
# File 'lib/risky.rb', line 76

def content_type
  "application/json"
end

.create(key = nil, values = {}, opts = {}) ⇒ Object



45
46
47
# File 'lib/risky.rb', line 45

def create(key = nil, values = {}, opts = {})
  new(key, values).save(opts)
end

.delete(key, opts = {}) ⇒ Object

Returns true when record deleted. Returns nil when record was not present to begin with.



100
101
102
103
# File 'lib/risky.rb', line 100

def delete(key, opts = {})
  return if key.nil?
  bucket.delete(key.to_s, opts)
end

.exists?(key) ⇒ Boolean

Does the given key exist in our bucket?

Returns:

  • (Boolean)


106
107
108
109
# File 'lib/risky.rb', line 106

def exists?(key)
  return if key.nil?
  bucket.exists? key.to_s
end

.find(key, opts = {}) ⇒ Object



33
34
35
# File 'lib/risky.rb', line 33

def find(key, opts = {})
  self[key.to_s, opts]
end

.find_all_by_key(keys) ⇒ Object



37
38
39
40
41
42
43
# File 'lib/risky.rb', line 37

def find_all_by_key(keys)
  return [] if keys.blank?

  keys.map!(&:to_s)
  results = bucket.get_many(keys)
  keys.map { |key| from_riak_object(results[key]) }.compact
end

.from_riak_object(riak_object) ⇒ Object

Fills in values from a Riak::RObject



112
113
114
115
116
117
118
119
120
# File 'lib/risky.rb', line 112

def from_riak_object(riak_object)
  return nil if riak_object.nil?

  n = new.load_riak_object riak_object

  # Callback
  n.after_load
  n
end

.get_or_new(*args) ⇒ Object

Gets an existing record or creates one.



123
124
125
# File 'lib/risky.rb', line 123

def get_or_new(*args)
  self[*args] or new(args.first)
end

Establishes methods for manipulating a single link with a given tag.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/risky.rb', line 128

def link(tag)
  tag = tag.to_s
  class_eval %Q{
    def #{tag}
      begin
        @riak_object.links.find do |l|
          l.tag == #{tag.inspect}
        end.key
      rescue NoMethodError
        nil
      end
    end

    def #{tag}=(link)
      @riak_object.links.reject! do |l|
        l.tag == #{tag.inspect}
      end
      if link
        @riak_object.links << link.to_link(#{tag.inspect})
      end
    end
  }
end

Establishes methods for manipulating a set of links with a given tag.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/risky.rb', line 153

def links(tag)
  tag = tag.to_s
  class_eval %Q{
    def #{tag}
      @riak_object.links.select do |l|
        l.tag == #{tag.inspect}
      end.map do |l|
        l.key
      end
    end

    def add_#{tag}(link)
      @riak_object.links << link.to_link(#{tag.inspect})
    end

    def remove_#{tag}(link)
      @riak_object.links.delete link.to_link(#{tag.inspect})
    end

    def clear_#{tag}
      @riak_object.links.delete_if do |l|
        l.tag == #{tag.inspect}
      end
    end

    def #{tag}_count
      @riak_object.links.select{|l| l.tag == #{tag.inspect}}.length
    end
  }
end

.map(*args) ⇒ Object

Mapreduce helper



185
186
187
# File 'lib/risky.rb', line 185

def map(*args)
  mr.map(*args)
end

.merge(versions) ⇒ Object

Merges n versions of a record together, for read-repair. Returns the merged record.



191
192
193
# File 'lib/risky.rb', line 191

def merge(versions)
  versions.first
end

.mr(keys = nil) ⇒ Object

Begins a mapreduce on this model’s bucket. If no keys are given, operates on the entire bucket. If keys are given, operates on those keys first.



198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/risky.rb', line 198

def mr(keys = nil)
  mr = Riak::MapReduce.new(riak)

  if keys
    # Add specific keys
    [*keys].compact.inject(mr) do |mr, key|
      mr.add @bucket_name, key.to_s
    end
  else
    # Add whole bucket
    mr.add @bucket_name
  end
end

.reduce(*args) ⇒ Object

MR helper.



213
214
215
# File 'lib/risky.rb', line 213

def reduce(*args)
  mr.reduce(*args)
end

.riakObject

The Riak::Client backing this model class.



218
219
220
221
222
223
224
225
226
227
# File 'lib/risky.rb', line 218

def riak
  Thread.current[:riak_client] ||=
    if @riak and @riak.respond_to?(:call)
      @riak.call(self)
    elsif @riak
      @riak
    else
      superclass.riak
    end
end

.riak!Object

Forces Riak client for this thread to be reset. If your @riak proc can choose between multiple hosts, calling this on failure will allow subsequent requests to proceed on another host.



232
233
234
235
# File 'lib/risky.rb', line 232

def riak!
  Thread.current[:riak_client] = nil
  riak
end

.riak=(client) ⇒ Object

Sets the Riak Client backing this model class. If client is a lambda (or anything responding to #call), it will be invoked to generate a new client every time Risky feels it is appropriate.



240
241
242
# File 'lib/risky.rb', line 240

def riak=(client)
  @riak = client
end

.value(value, opts = {}) ⇒ Object

Add a new value to this model. Values aren’t necessary; you can use Risky#[], but if you would like to cast values to/from JSON or specify defaults, you may:

:default => object (#clone is called for each new instance) :class => Time, Integer, etc. Inferred from default.class if present.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/risky.rb', line 250

def value(value, opts = {})
  value = value.to_s

  klass = if opts[:class]
    opts[:class]
  elsif opts.include? :default
    opts[:default].class
  else
    nil
  end
  values[value] = opts.merge(:class => klass)

  class_eval %Q{
    def #{value}
      @values[#{value.inspect}]
    end

    def #{value}=(value)
      @values[#{value.inspect}] = value
    end
  }
end

.valuesObject

A list of all values we track.



274
275
276
# File 'lib/risky.rb', line 274

def values
  @values ||= {}
end

Instance Method Details

#==(object) ⇒ Object Also known as: eql?



320
321
322
323
# File 'lib/risky.rb', line 320

def ==(object)
  object.class == self.class &&
    (object.key.present? && object.key == self.key || object.object_id == self.object_id)
end

#===(object) ⇒ Object



326
327
328
# File 'lib/risky.rb', line 326

def ===(object)
  object.is_a?(self.class)
end

#[](k) ⇒ Object

Access the values hash.



331
332
333
# File 'lib/risky.rb', line 331

def [](k)
  values[k]
end

#[]=(k, v) ⇒ Object

Access the values hash.



336
337
338
# File 'lib/risky.rb', line 336

def []=(k, v)
  values[k] = v
end

#after_createObject



340
341
# File 'lib/risky.rb', line 340

def after_create
end

#after_deleteObject



343
344
# File 'lib/risky.rb', line 343

def after_delete
end

#after_loadObject

Called when a riak object is used to populate the instance.



347
348
# File 'lib/risky.rb', line 347

def after_load
end

#after_saveObject



350
351
# File 'lib/risky.rb', line 350

def after_save
end

#as_json(opts = {}) ⇒ Object



353
354
355
356
357
# File 'lib/risky.rb', line 353

def as_json(opts = {})
  h = @values.merge(:key => key)
  h[:errors] = errors unless errors.empty?
  h
end

#before_createObject

Called before creation and validation



360
361
# File 'lib/risky.rb', line 360

def before_create
end

#before_deleteObject

Called before deletion



364
365
# File 'lib/risky.rb', line 364

def before_delete
end

#before_saveObject

Called before saving and before validation



368
369
# File 'lib/risky.rb', line 368

def before_save
end

#deleteObject

Delete this object in the DB and return self.



372
373
374
375
376
377
378
# File 'lib/risky.rb', line 372

def delete
  before_delete
  @riak_object.delete
  after_delete

  self
end

#errorsObject

A hash for errors on this object



381
382
383
# File 'lib/risky.rb', line 381

def errors
  @errors ||= {}
end

#idObject



438
439
440
441
442
# File 'lib/risky.rb', line 438

def id
  Integer(key)
rescue ArgumentError
  key
end

#id=(value) ⇒ Object



444
445
446
# File 'lib/risky.rb', line 444

def id=(value)
  self.key = value
end

#inspectObject



422
423
424
# File 'lib/risky.rb', line 422

def inspect
  "#<#{self.class} #{key} #{@values.inspect}>"
end

#keyObject



434
435
436
# File 'lib/risky.rb', line 434

def key
  @riak_object.key
end

#key=(key) ⇒ Object



426
427
428
429
430
431
432
# File 'lib/risky.rb', line 426

def key=(key)
  if key.nil?
    @riak_object.key = nil
  else
    @riak_object.key = key.to_s
  end
end

#load_riak_object(riak_object, opts = {:merge => true}) ⇒ Object

Replaces values and riak_object with data from riak_object.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/risky.rb', line 386

def load_riak_object(riak_object, opts = {:merge => true})
  if opts[:merge] and riak_object.conflict? and siblings = riak_object.siblings
    # Engage conflict resolution mode
    final = self.class.merge(
      siblings.map do |sibling|
        robject = Riak::RObject.new(sibling.bucket, sibling.key)
        robject.raw_data = sibling.raw_data
        robject.content_type = sibling.content_type
        # robject.siblings = [sibling]
        robject.vclock = sibling.vclock
        self.class.new.load_riak_object(robject, :merge => false)
      end
    )

    # Copy final values to self.
    final.instance_variables.each do |var|
      self.instance_variable_set(var, final.instance_variable_get(var))
    end

    self.merged = true
  else
    # Not merging
    self.values = self.class.cast(riak_object.data) rescue {}
    self.class.values.each do |k, v|
      if values[k].nil?
        values[k] = (v[:default].clone rescue v[:default])
      end
    end
    self.riak_object = riak_object
    self.new = false
    self.merged = false
  end

  self
end

#merged=(merged) ⇒ Object



448
449
450
# File 'lib/risky.rb', line 448

def merged=(merged)
  @merged = !!merged
end

#merged?Boolean

Has this model been merged from multiple siblings?

Returns:

  • (Boolean)


453
454
455
# File 'lib/risky.rb', line 453

def merged?
  @merged
end

#new=(new) ⇒ Object



457
458
459
# File 'lib/risky.rb', line 457

def new=(new)
  @new = !!new
end

#new?Boolean

Is this model freshly created; i.e. not saved in the database yet?

Returns:

  • (Boolean)


462
463
464
# File 'lib/risky.rb', line 462

def new?
  @new
end

#reload(opts = {}) ⇒ Object

Reload this model’s data from Riak. opts are passed to Riak::Bucket[]



468
469
470
471
472
473
474
475
476
477
478
# File 'lib/risky.rb', line 468

def reload(opts = {})
  # Get object from riak.
  riak_object = self.class.bucket[key, opts]

  # Load
  load_riak_object riak_object

  # Callback
  after_load
  self
end

#save(opts = {}) ⇒ Object

Saves this model.

Calls #validate and #valid? unless :validate is false.

Converts @values to_json and saves it to riak.

:w and :dw are also supported.



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/risky.rb', line 487

def save(opts = {})
  before_create if @new
  before_save

  unless opts[:validate] == false
    return false unless valid?
  end

  @riak_object.data = @values
  @riak_object.content_type = self.class.content_type

  store_opts = {}
  store_opts[:w] = opts[:w] if opts[:w]
  store_opts[:dw] = opts[:dw] if opts[:dw]
  @riak_object.store store_opts

  after_create if @new
  after_save

  @new = false

  self
end

#to_json(*a) ⇒ Object

This is provided for convenience; #save does not use this method, and you are free to override it.



525
526
527
# File 'lib/risky.rb', line 525

def to_json(*a)
  as_json.to_json(*a)
end

Returns a Riak::Link object pointing to this record.



530
531
532
# File 'lib/risky.rb', line 530

def to_link(*a)
  @riak_object.to_link(*a)
end

#update_attribute(attribute, value) ⇒ Object



511
512
513
514
# File 'lib/risky.rb', line 511

def update_attribute(attribute, value)
  self.send("#{attribute}=", value)
  self.save
end

#update_attributes(attributes) ⇒ Object



516
517
518
519
520
521
# File 'lib/risky.rb', line 516

def update_attributes(attributes)
  attributes.each do |attribute, value|
    self.send("#{attribute}=", value)
  end
  self.save
end

#valid?Boolean

Calls #validate and checks whether the errors hash is empty.

Returns:

  • (Boolean)


535
536
537
538
539
# File 'lib/risky.rb', line 535

def valid?
  @errors = {}
  validate
  @errors.empty?
end

#validateObject

Determines whether the model is valid. Sets the contents of #errors if invalid.



543
544
545
546
547
# File 'lib/risky.rb', line 543

def validate
  if key.blank?
    errors[:key] = 'is missing'
  end
end