Class: Couchbase::Model

Inherits:
Object show all
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 collapse

VERSION =
"0.2.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attrs = {}) ⇒ Model

Constructor for all subclasses of Couchbase::Model

Optionally takes a Hash of attribute value pairs.

Parameters:

  • attrs (Hash) (defaults to: {})

    attribute-value pairs

Since:

  • 0.0.1



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/couchbase/model.rb', line 373

def initialize(attrs = {})
  if attrs.respond_to?(:with_indifferent_access)
    attrs = attrs.with_indifferent_access
  end
  @id = attrs.delete(:id)
  @key = attrs.delete(:key)
  @value = attrs.delete(:value)
  @doc = attrs.delete(:doc)
  @meta = attrs.delete(:meta)
  @_attributes = ::Hash.new do |h, k|
    default = self.class.attributes[k]
    h[k] = if default.respond_to?(:call)
             default.call
           else
             default
           end
  end
  update_attributes(@doc || attrs)
end

Instance Attribute Details

#docObject (readonly)

Since:

  • 0.2.0



96
97
98
# File 'lib/couchbase/model.rb', line 96

def doc
  @doc
end

#idObject

Each model must have identifier

Since:

  • 0.0.1



87
88
89
# File 'lib/couchbase/model.rb', line 87

def id
  @id
end

#keyObject (readonly)

Since:

  • 0.2.0



90
91
92
# File 'lib/couchbase/model.rb', line 90

def key
  @key
end

#metaObject (readonly)

Since:

  • 0.2.0



99
100
101
# File 'lib/couchbase/model.rb', line 99

def meta
  @meta
end

#valueObject (readonly)

Since:

  • 0.2.0



93
94
95
# File 'lib/couchbase/model.rb', line 93

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)

Parameters:

  • name (Symbol, String)

    name of the attribute

Since:

  • 0.0.1



271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/couchbase/model.rb', line 271

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

.attributesHash

All defined attributes within a class.

Returns:

  • (Hash)

See Also:

Since:

  • 0.0.1



498
499
500
# File 'lib/couchbase/model.rb', line 498

def self.attributes
  @@attributes[self]
end

.connect(*options) ⇒ Couchbase::Bucket

Use custom connection options

Examples:

Choose specific bucket

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

Parameters:

  • options (String, Hash, Array)

    options for establishing connection.

Returns:

  • (Couchbase::Bucket)

See Also:

  • Bucket#initialize

Since:

  • 0.0.1



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

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

.create(*args) ⇒ Couchbase::Model

Create the model with given attributes

Parameters:

  • args (Hash)

    attribute-value pairs for the object

Returns:

Since:

  • 0.0.1



362
363
364
# File 'lib/couchbase/model.rb', line 362

def self.create(*args)
  new(*args).create
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

Parameters:

  • name (String, Symbol) (defaults to: nil)

    the name for the design document. By default underscored model name is used.

Returns:

  • (String)

    the effective design document

See Also:

Since:

  • 0.1.0



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/couchbase/model.rb', line 146

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 Couchbase::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_create_at, +by_session_id` and ‘total_views`.

Since:

  • 0.1.0



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/couchbase/model.rb', line 189

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).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'].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

Parameters:

  • id (String, Symbol)

    the record identifier

Returns:

  • (true, false)

    Whether or not the object with given id presented in the bucket.

Since:

  • 0.0.1



477
478
479
# File 'lib/couchbase/model.rb', line 477

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

.find(id) ⇒ Couchbase::Model

Find the model using id attribute

Examples:

Find model using id

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

Parameters:

  • id (String, Symbol)

    model identificator

Returns:

Raises:

  • (Couchbase::Error::NotFound)

    when given key isn’t exist

Since:

  • 0.0.1



332
333
334
335
336
337
# File 'lib/couchbase/model.rb', line 332

def self.find(id)
  if id && (res = bucket.get(id, :quiet => false, :extended => true))
    obj, flags, cas = res
    new({:id => id, :meta => {'flags' => flags, 'cas' => cas}}.merge(obj))
  end
end

.find_by_id(id) ⇒ Couchbase::Model?

Find the model using id attribute

Examples:

Find model using id

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

Parameters:

  • id (String, Symbol)

    model identificator

Returns:

  • (Couchbase::Model, nil)

    an instance of the model or nil if given key isn’t exist

Since:

  • 0.1.0



349
350
351
352
353
354
# File 'lib/couchbase/model.rb', line 349

def self.find_by_id(id)
  if id && (res = bucket.get(id, :quiet => true))
    obj, flags, cas = res
    new({:id => id, :meta => {'flags' => flags, 'cas' => cas}}.merge(obj))
  end
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...."
    


622
623
624
625
626
627
628
# File 'lib/couchbase/model.rb', line 622

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

Parameters:

  • algorithm (Symbol)

    (:sequential) one of the available algorithms.

Returns:

  • (Symbol)

See Also:

  • UUID#next

Since:

  • 0.0.1



251
252
253
# File 'lib/couchbase/model.rb', line 251

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

Parameters:

  • names (Symbol, String, Array)

    names of the views

  • options (Hash)

    options passed to the View

Since:

  • 0.0.1



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/couchbase/model.rb', line 306

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.

Returns:

  • (Array)

See Also:

Since:

  • 0.1.0



509
510
511
# File 'lib/couchbase/model.rb', line 509

def self.views
  @@views[self]
end

Instance Method Details

#attributesHash

All the attributes of the current instance

Returns:

  • (Hash)

Since:

  • 0.0.1



518
519
520
# File 'lib/couchbase/model.rb', line 518

def attributes
  @_attributes
end

#createCouchbase::Model

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

Returns:

Raises:

  • (Couchbase::Error::KeyExists)

    if model with the same id exists in the bucket

Since:

  • 0.0.1



405
406
407
408
409
# File 'lib/couchbase/model.rb', line 405

def create
  @id ||= Couchbase::Model::UUID.generator.next(1, model.thread_storage[:uuid_algorithm])
  model.bucket.add(@id, attributes_with_values)
  self
end

#deleteCouchbase::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

Returns:

Raises:

  • (Couchbase::Error::MissingId)

Since:

  • 0.0.1



450
451
452
453
454
455
# File 'lib/couchbase/model.rb', line 450

def delete
  raise Couchbase::Error::MissingId, "missing id attribute" unless @id
  model.bucket.delete(@id)
  @id = nil
  self
end

#exists?true, false

Check if this model exists in the bucket.

Returns:

  • (true, false)

    Whether or not this object presented in the bucket.

Since:

  • 0.0.1



487
488
489
# File 'lib/couchbase/model.rb', line 487

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

Returns:

  • (true, false)

    Whether or not this object has an id.

See Also:

Since:

  • 0.0.1



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

def new?
  !@id
end

#reloadModel

Reload all the model attributes from the bucket

Returns:

  • (Model)

    the latest model state

Raises:

Since:

  • 0.0.1



545
546
547
548
549
550
# File 'lib/couchbase/model.rb', line 545

def reload
  raise Couchbase::Error::MissingId, "missing id attribute" unless @id
  attrs = model.find(@id).attributes
  update_attributes(attrs)
  self
end

#saveCouchbase::Model

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

Returns:

Since:

  • 0.0.1



421
422
423
424
425
# File 'lib/couchbase/model.rb', line 421

def save
  return create if new?
  model.bucket.set(@id, attributes_with_values)
  self
end

#update(attrs) ⇒ Couchbase::Model

Update this object, optionally accepting new attributes.

Parameters:

  • attrs (Hash)

    Attribute value pairs to use for the updated version

Returns:

Since:

  • 0.0.1



434
435
436
437
# File 'lib/couchbase/model.rb', line 434

def update(attrs)
  update_attributes(attrs)
  save
end

#update_attributes(attrs) ⇒ Object

Update all attributes without persisting the changes.

Parameters:

  • attrs (Hash)

    attribute-value pairs.

Since:

  • 0.0.1



527
528
529
530
531
532
533
534
535
# File 'lib/couchbase/model.rb', line 527

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