README.md in couchbase-orm-1.1.1 vs README.md in couchbase-orm-2.0.0

- old
+ new

@@ -1,20 +1,19 @@ # Couchbase ORM for Rails -[![Build Status](https://secure.travis-ci.org/acaprojects/couchbase-orm.svg)](http://travis-ci.org/acaprojects/couchbase-orm) - ## 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: +```yaml common: &common - hosts: localhost + connection_string: couchbase://localhost username: dev_user password: dev_password development: <<: *common @@ -24,31 +23,45 @@ <<: *common bucket: dev_bucket_test # set these environment variables on your production server production: - hosts: <%= ENV['COUCHBASE_HOST'] || ENV['COUCHBASE_HOSTS'] %> - bucket: <%= ENV['COUCHBASE_BUCKET'] %> - username: <%= ENV['COUCHBASE_USER'] %> - password: <%= ENV['COUCHBASE_PASSWORD'] %> + 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: + +```ruby +# 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 ```ruby require 'couchbase-orm' class Post < CouchbaseOrm::Base - attribute :title, type: String - attribute :body, type: String - attribute :draft, type: Boolean + attribute :title, :string + attribute :body, :string + attribute :draft, :boolean end p = Post.new(id: 'hello-world', title: 'Hello world', draft: true) @@ -67,31 +80,61 @@ 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: +<!-- You can define connection options on per model basis: ```ruby class Post < CouchbaseOrm::Base - attribute :title, type: String - attribute :body, type: String - attribute :draft, type: Boolean + attribute :title, :string + attribute :body, :string + attribute :draft, :boolean connect bucket: 'blog', password: ENV['BLOG_BUCKET_PASSWORD'] end +``` --> + +## Typing + +The following types have been tested : + +- :string +- :integer +- :float +- :boolean +- :date +- :datetime (stored as iso8601, use precision: n to store more decimal precision) +- :timestamp (stored as integer) +- :encrypted + - see <https://docs.couchbase.com/couchbase-lite/current/c/field-level-encryption.html> + - You must store a string that can be encoded in json (not binary data), use base64 if needed +- :array (see below) +- :nested (see below) + +You can register other types in ActiveModel registry : + +```ruby + class DateTimeWith3Decimal < CouchbaseOrm::Types::DateTime + def serialize(value) + value&.iso8601(3) + end + end + + ActiveModel::Type.register(:datetime3decimal, DateTimeWith3Decimal) ``` ## Validations There are all methods from ActiveModel::Validations accessible in context of rails application. You can also enforce types using ruby [conversion methods](http://www.virtuouscode.com/2012/05/07/a-ruby-conversion-idiom/) ```ruby class Comment < Couchbase::Model - attribute :author, :body, type: String + attribute :author, :string + attribute :body, :string validates_presence_of :author, :body end ``` @@ -100,50 +143,157 @@ Views are defined in the model and typically just emit an attribute that can then be used for filtering results or ordering. ```ruby class Comment < CouchbaseOrm::Base - attribute :author, :body, type: String + attribute :author :string + attribute :body, :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 : +When using a compound key, the usage is the same, you just give the full key : ```ruby 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]') - 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 + # 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> -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` +```ruby + class Comment < CouchbaseOrm::Base19 + self.ignored_properties = [:old_name] # ignore old_name property in the model + self.properties_always_exists_in_document = true # use is null for nil value instead of not valued for performance purpose, only possible if all properties always exists in document + end +``` +You can specify `properties_always_exists_in_document` to true if all properties always exists in document, this will allow to use `is null` instead of `not valued` for nil value, this will improve performance. + +WARNING: If a document exists without a property, the query will failed! So you must be sure that all documents have all properties. + + +## N1ql + +Like views, it's possible to use N1QL to process some requests used for filtering results or ordering. + +```ruby + class Comment < CouchbaseOrm::Base + attribute :author, :string + attribute :body, :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 +``` + +## Basic Active Record like query engine + +```ruby +class Comment < CouchbaseOrm::Base + attribute :title, :string + attribute :author, :string + attribute :category, :string + attribute :ratings, :number +end + +Comment.where(author: "Anne McCaffrey", category: ['S-F', 'Fantasy']).not(ratings: 0).order(:title).limit(10) + +# Relation can be composed as in AR: + +amc_comments = Comment.where(author: "Anne McCaffrey") + +amc_comments.count + +amc_sf_comments = amc_comments.where(category: 'S-F') + +# pluck is available, but will query all object fields first + +Comment.pluck(:title, :ratings) + +# To load the ids without loading the models + +Comment.where(author: "David Eddings").ids + +# To delete all the models of a relation + +Comment.where(ratings: 0).delete_all +``` + +## scopes + +Scopes can be written as class method, scope method is not implemented yet. +They can be chained as in AR or mixed with relation methods. + +```ruby +class Comment < CouchbaseOrm::Base + attribute :title, :string + attribute :author, :string + attribute :category, :string + attribute :ratings, :number + + def self.by_author(author) + where(author: author) + end +end + +Comment.by_author("Anne McCaffrey").where(category: 'S-F').not(ratings: 0).order(:title).limit(10) +``` + +## Operators + +Several operators are available to filter numerical results : \_gt, \_lt, \_gte, \_lte, \_ne + + ```ruby + Comment.where(ratings: {_gt: 3}) + ``` + +## Range in the where + +You can specify a Range of date or intger in the where clause + + ```ruby + Person.where(birth_date: DateTime.new(1980, 1, 1)..DateTime.new(1990, 1, 1)) + Person.where(age: 10..20) + + Person.where(age: 10...20) # to exclude the upper bound + ``` + ## Associations and Indexes There are common active record helpers available for use `belongs_to` and `has_many` ```ruby @@ -153,24 +303,80 @@ class Author < CouchbaseOrm::Base has_many :comments, dependent: :destroy # You can ensure an attribute is unique for this model - attribute :email, type: String + attribute :email, :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: + ```ruby + class Comment < CouchbaseOrm::Base + belongs_to :author + end + + class Author < CouchbaseOrm::Base + has_many :comments, type: :n1ql, dependent: :destroy + end + ``` + +## Nested + +Attributes can be of type nested, they must specify a type of NestedDocument. +The NestedValidation triggers nested validation on parent validation. + +```ruby + class Address < CouchbaseOrm::NestedDocument + attribute :road, :string + attribute :city, :string + validates :road, :city, presence: true + end + + class Author < CouchbaseOrm::Base + attribute :address, :nested, type: Address + validates :address, nested: true + end +``` + +Model can be queried using the nested attributes + +```ruby + Author.where(address: {road: '1 rue de la paix', city: 'Paris'}) +``` + +## Array + +Attributes can be of type array, they must contain something that can be serialized and deserialized to/from JSON. +You can enforce the type of array elements. The type can be a NestedDocument + +```ruby + class Book < CouchbaseOrm::NestedDocument + attribute :name, :string + validates :name, presence: true + end + + class Author < CouchbaseOrm::Base + attribute things, :array + attribute flags, :array, type: :string + attribute books, :array, type: Book + + validates :books, nested: true + end +``` + ## Performance Comparison with Couchbase-Ruby-Model Basically we migrated an application from [Couchbase Ruby Model](https://github.com/couchbase/couchbase-ruby-model) to [Couchbase-ORM](https://github.com/acaprojects/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` +- 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 | | :--- | :--- | :--- |