Class: Couchbase::Model

Inherits:
Object show all
Includes:
ActiveModel
Defined in:
lib/couchbase/model.rb,
lib/couchbase/model/uuid.rb,
lib/couchbase/model/version.rb,
lib/couchbase/model/configuration.rb

Overview

Declarative layer for Couchbase gem

You can also let the library generate the unique identifier for you:

p = Post.create(:title => 'How to generate ID',
                :body => 'Open up the editor...')
p.id        #=> "74f43c3116e788d09853226603000809"

There are several algorithms available. By default it use `:sequential` algorithm, but you can change it to more suitable one for you:

class Post < Couchbase::Model
  attribute :title
  attribute :body
  attribute :draft

  uuid_algorithm :random
end

You can define connection options on per model basis:

class Post < Couchbase::Model
  attribute :title
  attribute :body
  attribute :draft

  connect :port => 80, :bucket => 'blog'
end

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."

Defined Under Namespace

Modules: Configuration Classes: UUID

Constant Summary

VERSION =
'0.5.4'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ActiveModel

#==, #eql?, #hash, #to_model

Constructor Details

#initialize(attrs = {}) ⇒ Model

Constructor for all subclasses of Couchbase::Model

Optionally takes a Hash of attribute value pairs.

Since:

  • 0.0.1



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/couchbase/model.rb', line 478

def initialize(attrs = {})
  @errors = ::ActiveModel::Errors.new(self) if defined?(::ActiveModel)
  @_attributes = ::Hash.new do |h, k|
    default = self.class.attributes[k]
    h[k] = if default.respond_to?(:call)
             default.call
           else
             default
           end
  end
  case attrs
  when Hash
    if defined?(HashWithIndifferentAccess) && !attrs.is_a?(HashWithIndifferentAccess)
      if attrs.respond_to?(:with_indifferent_access)
        attrs = attrs.with_indifferent_access
      end
    end
    @id = attrs.delete(:id)
    @key = attrs.delete(:key)
    @value = attrs.delete(:value)
    @doc = attrs.delete(:doc)
    @meta = attrs.delete(:meta)
    @raw = attrs.delete(:raw)
    update_attributes(@doc || attrs)
    @previously_changed = nil
    @changed_attributes.clear unless @changed_attributes.nil?
  else
    @raw = attrs
  end
end

Instance Attribute Details

#docObject (readonly)

Since:

  • 0.2.0



118
119
120
# File 'lib/couchbase/model.rb', line 118

def doc
  @doc
end

#errorsObject (readonly)

Since:

  • 0.4.5



124
125
126
# File 'lib/couchbase/model.rb', line 124

def errors
  @errors
end

#idObject

Each model must have identifier

Since:

  • 0.0.1



109
110
111
# File 'lib/couchbase/model.rb', line 109

def id
  @id
end

#keyObject (readonly)

Since:

  • 0.2.0



112
113
114
# File 'lib/couchbase/model.rb', line 112

def key
  @key
end

#metaObject (readonly)

Since:

  • 0.2.0



121
122
123
# File 'lib/couchbase/model.rb', line 121

def meta
  @meta
end

#rawObject (readonly)

Since:

  • 0.4.5



127
128
129
# File 'lib/couchbase/model.rb', line 127

def raw
  @raw
end

#valueObject (readonly)

Since:

  • 0.2.0



115
116
117
# File 'lib/couchbase/model.rb', line 115

def value
  @value
end

Class Method Details

.attribute(*names) ⇒ Object

Defines an attribute for the model

Examples:

Define some attributes for a model

class Post < Couchbase::Model
  attribute :title
  attribute :body
  attribute :published_at
end

post = Post.new(:title => 'Hello world',
                :body => 'This is the first example...',
                :published_at => Time.now)

Since:

  • 0.0.1



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/couchbase/model.rb', line 318

def self.attribute(*names)
  options = {}
  if names.last.is_a?(Hash)
    options = names.pop
  end
  names.each do |name|
    name = name.to_sym
    attributes[name] = options[:default]
    next if self.instance_methods.include?(name)
    define_method(name) do
      read_attribute(name)
    end
    define_method(:#{name}=") do |value|
      write_attribute(name, value)
    end
  end
end

.attributesHash

All defined attributes within a class.

See Also:

Since:

  • 0.0.1



672
673
674
675
676
677
678
# File 'lib/couchbase/model.rb', line 672

def self.attributes
  @attributes ||= if self == Model
                    @@attributes.dup
                  else
                    couchbase_ancestor.attributes.dup
                  end
end

.belongs_to(name, options = {}) ⇒ Object

Defines a belongs_to association for the model

Examples:

Define some association for a model

class Brewery < Couchbase::Model
  attribute :name
end

class Beer < Couchbase::Model
  attribute :name, :brewery_id
  belongs_to :brewery
end

Beer.find("heineken").brewery.name

Options Hash (options):

  • :class_name (String, Symbol)

    the name of the association class

Since:

  • 0.3.0



389
390
391
392
393
394
395
396
# File 'lib/couchbase/model.rb', line 389

def self.belongs_to(name, options = {})
  ref = "#{name}_id"
  attribute(ref)
  assoc = (options[:class_name] || name).to_s.camelize.constantize
  define_method(name) do
    assoc.find(self.send(ref))
  end
end

.connect(*options) ⇒ Couchbase::Bucket

Use custom connection options

Examples:

Choose specific bucket

class Post < Couchbase::Model
  connect :bucket => 'posts'
  ...
end

See Also:

  • Bucket#initialize

Since:

  • 0.0.1



150
151
152
# File 'lib/couchbase/model.rb', line 150

def self.connect(*options)
  self.bucket = Couchbase.connect(*options)
end

.couchbase_ancestorObject

Returns the first ancestor that is also a Couchbase::Model ancestor.

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


698
699
700
701
702
# File 'lib/couchbase/model.rb', line 698

def self.couchbase_ancestor
  ancestors[1..-1].each do |ancestor|
    return ancestor if ancestor.ancestors.include?(Couchbase::Model)
  end
end

.create(*args) ⇒ Couchbase::Model, false

Create the model with given attributes

Since:

  • 0.0.1



458
459
460
# File 'lib/couchbase/model.rb', line 458

def self.create(*args)
  new(*args).create
end

.create!(*args) ⇒ Object

Creates an object just like {Model{Model.create but raises an exception if the record is invalid.

Raises:

  • (Couchbase::Error::RecordInvalid)

    if the instance is invalid

Since:

  • 0.5.1



467
468
469
# File 'lib/couchbase/model.rb', line 467

def self.create!(*args)
  new(*args).create!
end

.defaults(options = nil) ⇒ Object

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


188
189
190
191
192
193
194
# File 'lib/couchbase/model.rb', line 188

def self.defaults(options = nil)
  if options
    @_defaults = options
  else
    @_defaults || {}
  end
end

.design_document(name = nil) ⇒ String

Associate custom design document with the model

Design document is the special document which contains views, the chunks of code for building map/reduce indexes. When this method called without argument, it just returns the effective design document name.

Examples:

Choose specific design document name

class Post < Couchbase::Model
  design_document :my_posts
  ...
end

See Also:

Since:

  • 0.1.0



174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/couchbase/model.rb', line 174

def self.design_document(name = nil)
  if name
    @_design_doc = name.to_s
  else
    @_design_doc ||= begin
                       name = self.name.dup
                       name.gsub!(/::/, '_')
                       name.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
                       name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
                       name.downcase!
                     end
  end
end

.ensure_design_document!Object

Ensure that design document is up to date.

This method also cares about organizing view in separate javascript files. The general structure is the following (+[root]+ is the directory, one of the Model::Configuration.design_documents_paths):

[root]
|
`- link
|  |
|  `- by_created_at
|  |  |
|  |  `- map.js
|  |
|  `- by_session_id
|  |  |
|  |  `- map.js
|  |
|  `- total_views
|  |  |
|  |  `- map.js
|  |  |
|  |  `- reduce.js

The directory structure above demonstrate layout for design document with id _design/link and three views: by_created_at, +by_session_id` and `total_views`.

Since:

  • 0.1.0



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/couchbase/model.rb', line 225

def self.ensure_design_document!
  unless Configuration.design_documents_paths
    raise 'Configuration.design_documents_path must be directory'
  end

  doc = {'_id' => "_design/#{design_document}", 'views' => {}}
  digest = Digest::MD5.new
  mtime = 0
  views.each do |name, _|
    doc['views'][name] = {}
    doc['spatial'] = {}
    ['map', 'reduce', 'spatial'].each do |type|
      Configuration.design_documents_paths.each do |path|
        ff = File.join(path, design_document.to_s, name.to_s, "#{type}.js")
        if File.file?(ff)
          contents = File.read(ff).gsub(/^\s*\/\/.*$\n\r?/, '').strip
          next if contents.empty?
          mtime = [mtime, File.mtime(ff).to_i].max
          digest << contents
          case type
          when 'map', 'reduce'
            doc['views'][name][type] = contents
          when 'spatial'
            doc['spatial'][name] = contents
          end
          break # pick first matching file
        end
      end
    end
  end

  doc['views'].delete_if {|_, v| v.empty? }
  doc.delete('spatial') if doc['spatial'] && doc['spatial'].empty?
  doc['signature'] = digest.to_s
  doc['timestamp'] = mtime
  if doc['signature'] != thread_storage[:signature] && doc['timestamp'] > thread_storage[:timestamp].to_i
    current_doc = bucket.design_docs[design_document.to_s]
    if current_doc.nil? || (current_doc['signature'] != doc['signature'] && doc['timestamp'] > current_doc[:timestamp].to_i)
      bucket.save_design_doc(doc)
      current_doc = doc
    end
    thread_storage[:signature] = current_doc['signature']
    thread_storage[:timestamp] = current_doc['timestamp'].to_i
  end
end

.exists?(id) ⇒ true, false

Check if the key exists in the bucket

Since:

  • 0.0.1



651
652
653
# File 'lib/couchbase/model.rb', line 651

def self.exists?(id)
  !!bucket.get(id, :quiet => true)
end

.find(*id) ⇒ Couchbase::Model, Array

Find the model using id attribute

Examples:

Find model using id

post = Post.find('the-id')

Find multiple models using id

post = Post.find('one', 'two')

Raises:

  • (Couchbase::Error::NotFound)

    when given key isn't exist

Since:

  • 0.0.1



427
428
429
# File 'lib/couchbase/model.rb', line 427

def self.find(*id)
  _find(false, *id)
end

.find_by_id(*id) ⇒ Couchbase::Model, ...

Find the model using id attribute

Unlike find, this method won't raise Error::NotFound error when key doesn't exist in the bucket

Examples:

Find model using id

post = Post.find_by_id('the-id')

Find multiple models using id

posts = Post.find_by_id(['the-id', 'the-id2'])

Since:

  • 0.1.0



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

def self.find_by_id(*id)
  _find(true, *id)
end

.inspectObject

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


824
825
826
827
828
829
830
# File 'lib/couchbase/model.rb', line 824

def self.inspect
  buf = "#{name}"
  if self != Couchbase::Model
    buf << "(#{['id', attributes.map(&:first)].flatten.join(', ')})"
  end
  buf
end

.uuid_algorithm(algorithm) ⇒ Symbol

Choose the UUID generation algorithms

Examples:

Select :random UUID generation algorithm

class Post < Couchbase::Model
  uuid_algorithm :random
  ...
end

See Also:

  • UUID#next

Since:

  • 0.0.1



287
288
289
# File 'lib/couchbase/model.rb', line 287

def self.uuid_algorithm(algorithm)
  self.thread_storage[:uuid_algorithm] = algorithm
end

.view(*names) ⇒ Object

Defines a view for the model

Examples:

Define some views for a model

class Post < Couchbase::Model
  view :all, :published
  view :by_rating, :include_docs => false
end

post = Post.find("hello")
post.by_rating.each do |r|
  # ...
end

Since:

  • 0.0.1



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/couchbase/model.rb', line 353

def self.view(*names)
  options = {:wrapper_class => self, :include_docs => true}
  if names.last.is_a?(Hash)
    options.update(names.pop)
  end
  is_spatial = options.delete(:spatial)
  names.each do |name|
    path = '_design/%s/_%s/%s' % [design_document, is_spatial ? 'spatial' : 'view', name]
    views[name] = lambda do |*params|
      params = options.merge(params.first || {})
      View.new(bucket, path, params)
    end
    singleton_class.send(:define_method, name, &views[name])
  end
end

.viewsArray

All defined views within a class.

See Also:

Since:

  • 0.1.0



687
688
689
690
691
692
693
# File 'lib/couchbase/model.rb', line 687

def self.views
  @views ||= if self == Model
               @@views.dup
             else
               couchbase_ancestor.views.dup
             end
end

Instance Method Details

#as_json(options = {}) ⇒ Hash

Format the model for use in a JSON response

Since:

  • 0.5.2



750
751
752
# File 'lib/couchbase/model.rb', line 750

def as_json(options = {})
  attributes.merge({:id => @id}).as_json(options)
end

#attributesHash

All the attributes of the current instance

Since:

  • 0.0.1



709
710
711
# File 'lib/couchbase/model.rb', line 709

def attributes
  @_attributes || {}
end

#create(options = {}) ⇒ Couchbase::Model, false

Create this model and assign new id if necessary

Examples:

Create the instance of the Post model

p = Post.new(:title => 'Hello world', :draft => true)
p.create

Raises:

  • (Couchbase::Error::KeyExists)

    if model with the same id exists in the bucket

Since:

  • 0.0.1



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/couchbase/model.rb', line 521

def create(options = {})
  @id ||= Couchbase::Model::UUID.generator.next(1, model.thread_storage[:uuid_algorithm])
  if respond_to?(:valid?) && !valid?
    return false
  end
  options = model.defaults.merge(options)
  value = (options[:format] == :plain) ?  @raw : attributes_with_values
  unless @meta
    @meta = {}
    if @meta.respond_to?(:with_indifferent_access)
      @meta = @meta.with_indifferent_access
    end
  end
  @meta['cas'] = model.bucket.add(@id, value, options)
  self
end

#create!(options = {}) ⇒ Object

Creates an object just like Couchbase::Model.{Model{Model#create but raises an exception if the record is invalid.

Raises:

  • (Couchbase::Error::RecordInvalid)

    if the instance is invalid

Since:

  • 0.5.1



544
545
546
# File 'lib/couchbase/model.rb', line 544

def create!(options = {})
  create(options) || raise(Couchbase::Error::RecordInvalid.new(self))
end

#delete(options = {}) ⇒ Couchbase::Model

Note:

This method will reset id attribute

Delete this object from the bucket

Examples:

Delete the Post model

p = Post.find('hello-world')
p.delete

Raises:

  • (Couchbase::Error::MissingId)

Since:

  • 0.0.1



618
619
620
621
622
623
624
# File 'lib/couchbase/model.rb', line 618

def delete(options = {})
  raise Couchbase::Error::MissingId, 'missing id attribute' unless @id
  model.bucket.delete(@id, options)
  @id = nil
  @meta = nil
  self
end

#exists?true, false

Check if this model exists in the bucket.

Since:

  • 0.0.1



661
662
663
# File 'lib/couchbase/model.rb', line 661

def exists?
  model.exists?(@id)
end

#new?true, false

Note:

true doesn't mean that record exists in the database

Check if the record have id attribute

See Also:

Since:

  • 0.0.1



635
636
637
# File 'lib/couchbase/model.rb', line 635

def new?
  !@id
end

#persisted?true, false

Returns Where on on this object persisted in the storage

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


640
641
642
# File 'lib/couchbase/model.rb', line 640

def persisted?
  !!@id
end

#read_attribute(attr_name) ⇒ Object Also known as: []

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


291
292
293
# File 'lib/couchbase/model.rb', line 291

def read_attribute(attr_name)
  @_attributes[attr_name]
end

#reloadModel

Reload all the model attributes from the bucket

Raises:

Since:

  • 0.0.1



736
737
738
739
740
741
742
# File 'lib/couchbase/model.rb', line 736

def reload
  raise Couchbase::Error::MissingId, 'missing id attribute' unless @id
  pristine = model.find(@id)
  update_attributes(pristine.attributes)
  @meta[:cas] = pristine.meta[:cas]
  self
end

#save(options = {}) ⇒ Couchbase::Model, false

Create or update this object based on the state of #new?.

Examples:

Update the Post model

p = Post.find('hello-world')
p.draft = false
p.save

Use CAS value for optimistic lock

p = Post.find('hello-world')
p.draft = false
p.save('cas' => p.meta['cas'])

Since:

  • 0.0.1



568
569
570
571
572
573
574
575
576
577
578
579
# File 'lib/couchbase/model.rb', line 568

def save(options = {})
  return create(options) unless @meta
  if respond_to?(:valid?) && !valid?
    return false
  end
  options = model.defaults.merge(options)
  value = (options[:format] == :plain) ?  @raw : attributes_with_values
  @meta['cas'] = model.bucket.replace(@id, value, options)
  @previously_changed = changes
  @changed_attributes.clear unless @changed_attributes.nil?
  self
end

#save!(options = {}) ⇒ Object

Creates an object just like Couchbase::Model.{Model{Model#save but raises an exception if the record is invalid.

Raises:

  • (Couchbase::Error::RecordInvalid)

    if the instance is invalid

Since:

  • 0.5.1



587
588
589
# File 'lib/couchbase/model.rb', line 587

def save!(options = {})
  save(options) || raise(Couchbase::Error::RecordInvalid.new(self))
end

#to_keyObject

Redefine (if exists) #to_key to use #key if #id is missing

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


846
847
848
849
# File 'lib/couchbase/model.rb', line 846

def to_key
  keys = [id || key]
  keys.empty? ? nil : keys
end

#to_paramObject

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


851
852
853
854
855
856
# File 'lib/couchbase/model.rb', line 851

def to_param
  keys = to_key
  if keys && !keys.empty?
    keys.join('-')
  end
end

#update(attrs, options = {}) ⇒ Couchbase::Model

Update this object, optionally accepting new attributes.

Since:

  • 0.0.1



600
601
602
603
# File 'lib/couchbase/model.rb', line 600

def update(attrs, options = {})
  update_attributes(attrs)
  save(options)
end

#update_attributes(attrs) ⇒ Object

Update all attributes without persisting the changes.

Since:

  • 0.0.1



718
719
720
721
722
723
724
725
726
# File 'lib/couchbase/model.rb', line 718

def update_attributes(attrs)
  if id = attrs.delete(:id)
    @id = id
  end
  attrs.each do |key, value|
    setter = :#{key}="
    send(setter, value) if respond_to?(setter)
  end
end

#write_attribute(attr_name, value) ⇒ Object Also known as: []=

Since:

  • 0.0.1

    require 'couchbase/model'

    class Post < Couchbase::Model

    attribute :title
    attribute :body
    attribute :draft

    end

    p = Post.new(:id => 'hello-world',

    :title => 'Hello world',
    :draft => true)

    p.save p = Post.find('hello-world') p.body = “Once upon the times.…” p.save p.update(:draft => false) Post.bucket.get('hello-world') #=> world”, “draft”=>false,

    #    "body"=>"Once upon the times...."


296
297
298
299
# File 'lib/couchbase/model.rb', line 296

def write_attribute(attr_name, value)
  attribute_will_change!(attr_name) unless @_attributes[attr_name] == value
  @_attributes[attr_name] = value
end