Skip to content

A Couchbase ORM based on ActiveModel for Rails compatibility

License

Notifications You must be signed in to change notification settings

avsej/couchbase-orm

 
 

Repository files navigation

Couchbase ORM for Rails

Build Status

Rails integration

To generate config you can use rails generate couchbase_orm:config:

$ rails generate couchbase_orm:config dev_bucket dev_user dev_password
  => create  config/couchbase.yml

It will generate this config/couchbase.yml for you:

    common: &common
      connection_string: couchbase://localhost
      username: dev_user
      password: dev_password

    development:
      <<: *common
      bucket: dev_bucket

    test:
      <<: *common
      bucket: dev_bucket_test

    # set these environment variables on your production server
    production:
      connection_string: <%= ENV['COUCHBASE_CONNECTION_STRING'] %>
      bucket: <%= ENV['COUCHBASE_BUCKET'] %>
      username: <%= ENV['COUCHBASE_USER'] %>
      password: <%= ENV['COUCHBASE_PASSWORD'] %>

Setup without Rails

If you are not using Rails, you can configure couchbase-orm with an initializer:

# config/initializers/couchbase_orm.rb
CouchbaseOrm::Connection.config = {
  connection_string: "couchbase://localhost"
  username: "dev_user"
  password: "dev_password"
  bucket: "dev_bucket"
}

Views are generated on application load if they don't exist or mismatch. This works fine in production however by default in development models are lazy loaded.

# config/environments/development.rb
config.eager_load = true

Examples

    require 'couchbase-orm'

    class Post < CouchbaseOrm::Base
      attribute :title, type: String
      attribute :body,  type: String
      attribute :draft, type: Boolean
    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')  #=> {"title"=>"Hello world", "draft"=>false,
                                    #    "body"=>"Once upon the times...."}

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        #=> "post-abcDE34"

You can define connection options on per model basis:

    class Post < CouchbaseOrm::Base
      attribute :title, type: String
      attribute :body,  type: String
      attribute :draft, type: Boolean

      connect bucket: 'blog', password: ENV['BLOG_BUCKET_PASSWORD']
    end

Validations

There are all methods from ActiveModel::Validations accessible in context of rails application. You can also enforce types using ruby conversion methods

    class Comment < Couchbase::Model
      attribute :author, :body, type: String

      validates_presence_of :author, :body
    end

Views (aka Map/Reduce indexes)

Views are defined in the model and typically just emit an attribute that can then be used for filtering results or ordering.

    class Comment < CouchbaseOrm::Base
      attribute :author, :body, type: String
      view :all # => emits :id and will return all comments
      view :by_author, emit_key: :author

      # Generates two functions:
      # * the by_author view above
      # * def find_by_author(author); end
      index_view :author

      # You can make compound keys by passing an array to :emit_key
      # this allow to query by read/unread comments
      view :by_read, emit_key: [:user_id, :read]
      # this allow to query by view_count
      view :by_view_count, emit_key: [:user_id, :view_count]

      validates_presence_of :author, :body
    end

You can use Comment.find_by_author('name') to obtain all the comments by a particular author. The same thing, using the view directly would be: Comment.by_author(key: 'name')

When using a compound key, the usage is the same, you just give the full key :

   Comment.by_read(key: '["'+user_id+'",false]') # gives all unread comments for one particular user

   # or even a range !

   Comment.by_view_count(startkey: '["'+user_id+'",10]', endkey: '["'+user_id+'",20]') # gives all comments that have been seen more than 10 times but less than 20

Check this couchbase help page to learn more on what's possible with compound keys : https://developer.couchbase.com/documentation/server/3.x/admin/Views/views-translateSQL.html

Ex : Compound keys allows to decide the order of the results, and you can reverse it by passing descending: true

N1ql

Like views, it's possible to use N1QL to process some requests used for filtering results or ordering.

    class Comment < CouchbaseOrm::Base
      attribute :author, :body, type: String
      n1ql :by_author, emit_key: :author

      # Generates two functions:
      # * the by_author view above
      # * def find_by_author(author); end
      index_n1ql :author

      # You can make compound keys by passing an array to :emit_key
      # this allow to query by read/unread comments
      n1ql :by_read, emit_key: [:user_id, :read]
      # this allow to query by view_count
      n1ql :by_view_count, emit_key: [:user_id, :view_count]

      validates_presence_of :author, :body
    end

Whatever the record, it's possible to execute a N1QL request with:

Comment.bucket.n1ql.select('RAW meta(ui).id').from('bucket').where('author="my_value"').order_by('view_count DESC').results

Associations and Indexes

There are common active record helpers available for use belongs_to and has_many

    class Comment < CouchbaseOrm::Base
        belongs_to :author
    end

    class Author < CouchbaseOrm::Base
        has_many :comments, dependent: :destroy

        # You can ensure an attribute is unique for this model
        attribute :email, type: String
        ensure_unique :email
    end

By default, has_many uses a view for association, but you can define a type option to specify an association using N1QL instead:

    class Comment < CouchbaseOrm::Base
        belongs_to :author
    end

    class Author < CouchbaseOrm::Base
        has_many :comments, type: :n1ql, dependent: :destroy
    end

Performance Comparison with Couchbase-Ruby-Model

Basically we migrated an application from Couchbase Ruby Model to Couchbase-ORM (this project)

  • Rails 5 production
  • Puma as the webserver
  • Running on a 2015 Macbook Pro
  • Performance test: siege -c250 -r10 http://localhost:3000/auth/authority

The request above pulls the same database document each time and returns it. A simple O(1) operation.

Stat Couchbase Ruby Model Couchbase-ORM
Transactions 2500 hits 2500 hits
Elapsed time 12.24 secs 6.82 secs
Response time 0.88 secs 0.34 secs
Transaction rate 204.25 trans/sec 366.57 trans/sec
Request Code ruby-model-app couch-orm-app

About

A Couchbase ORM based on ActiveModel for Rails compatibility

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 99.0%
  • Shell 1.0%