Search:

Search all manuals
Search this manual
Manual
Couchbase Client Library Ruby 1.0
Additional Resources
Community Wiki
Community Forums
Couchbase SDKs
Parent Section
2.3 Not your mother's ActiveRecord
Chapter Sections
Chapters

2.3.3. Defining a Couchbase Model

2.3.3.1. Couchbase + ActiveModel
2.3.3.2. The Link Model

Now that we know what kind of an API we want to expose from our Link Model, let's define it.

Listing 7: app/models/link.rb

class Link

  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend ActiveModel::Callbacks
  extend ActiveModel::Naming

  # Couch Model

  attr_accessor :url, :key, :views, :session_id, :created_at

  define_model_callbacks :save
  validates :url, :presence => true, :url => {:allow_nil => true, :message => "This is not a valid URL"}
  before_save :generate_key

  def generate_key
    while self.key.nil?
      random = SecureRandom.hex(2)
      self.key = random if self.class.find(random).nil?
    end
  end

  # ActiveModel

  def initialize(attributes = {})
    @errors = ActiveModel::Errors.new(self)
    attributes.each do |name, value|
      setter = "#{name}="
      next unless respond_to?(setter)
      send(setter, value)
    end
    self.views ||= 0
    self.created_at ||= Time.zone.now
  end

  def to_param
    self.key
  end

  def persisted?
    return false unless (key && valid?)
    # TODO need a better way to track if an object is *dirty* or not...
    self.class.find(key).url == self.url
  end

  def save
    return false unless valid?
    run_callbacks :save do
      Couch.client.set(self.key, {
        :type => self.class.to_s.downcase,
        :url => self.url,
        :key => self.key,
        :views => self.views,
        :session_id => self.session_id,
        :created_at => self.created_at
      })
    end
    true
  end

  def self.find(key)
    return nil unless key
    begin
      doc = Couch.client.get(key)
      self.new(doc)
    rescue Couchbase::Error::NotFound => e
      nil
    end
  end

end

Might seem like a lot to take in at once, let's break it down into the three main responsibilities of the Class.

2.3.3.1. Couchbase + ActiveModel

Listing 7a: app/models/link.rb [line:3..6]

include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Callbacks
extend ActiveModel::Naming

Firstly we include all the aspects of ActiveModel that we would like to use.

Listing 7b: app/models/link.rb [line:8]

attr_accessor :url, :key, :views, :session_id, :created_at

Our model needs some Attributes so we define them.

Listing 7c: app/models/link.rb [line:23..32]

def initialize(attributes = {})
    @errors = ActiveModel::Errors.new(self)
    attributes.each do |name, value|
      next unless @@keys.include?(name.to_sym)
      send("#{name}=", value)
    end
    self.views ||= 0
    self.created_at ||= Time.zone.now
  end

In our initialize method we are doing the following:

  • Initializing an ActiveModel::Errors object that will work with the validations we'll define later.

  • Iterating through the Hash of attributes passed in. We check if our list of attribute keys and if it matches we assign that attribute to the passed in value.

Listing 7d: app/models/link.rb [line:34..36]

def to_param
             self.key
           end

To work with rails link_to methods we need to implement this method. As we would like to use our generated key.

Listing 7e: app/models/link.rb [line:38..42]

def persisted?
             return false unless (key && valid?)
             # TODO use a better way to track if an object is *dirty* or not...
             self.class.find(key).url == self.url
           end

So that our Link objects know wether they are a new? object or not, we implemented this method. There are better ways to track this, just to demonstrate things working, once a Link is valid? we are checking to see if the object has been persisted and that the url matches.

Listing 7f: app/models/link.rb [line:44..56]

def save
   return false unless valid?
   run_callbacks :save do
     # TODO should client.set return nil if successful? don't think so
     Couch.client.set(self.key, {
       :type => self.class.to_s.downcase,
       :url => self.url,
       :key => self.key,
       :views => self.views,
       :session_id => self.session_id,
       :created_at => self.created_at
     })
   end
 end

Saving a complex document to Couchbase is a simple as passing a Hash of attributes as the second parameter to client.set(key, value).

Listing 7g: app/models/link.rb [line:58..66]

def self.find(key)
   return nil unless key
   begin
     doc = Couch.client.get(key)
     self.new(doc)
   rescue Couchbase::Error::NotFound => e
     nil
   end
 end

Here we've implemented a simple find method that will get a given document from couchbase based on the key. It's worth noting that Couchbase will raise an error if the document you're trying to access doesn't exist in which case we rescue and return a nil.

2.3.3.2. The Link Model

Everything we've done up to now is providing a bit of framework to our code to allow us to create our Link object.

Listing 7h: app/models/link.rb [line:8..19]

attr_accessor :url, :key, :views, :session_id, :created_at

define_model_callbacks :save
validates :url, :presence => true, :url => {:allow_nil => true}
before_save :generate_key

def generate_key
  while self.key.nil?
    random = SecureRandom.hex(2)
    self.key = random if self.class.find(random).nil?
  end
end

What we are left with here is quite simple, we've already mentioned defining the attributes. Next, we're telling ActiveModel that it should give us a callback to bind to for the save method of a Link. Next we setup a validation on the url attribute, we're using the built-in presence validator and we've included a third party validation by way of thevalidate_url Gem. Finally we setup our save callback to call the generate_key method which takes care of creating a unique short key for our Links.