Class: CouchRest::Model
- Inherits:
-
Hash
- Object
- Hash
- CouchRest::Model
- Includes:
- Extlib::Hook
- Defined in:
- lib/couchrest/core/model.rb
Overview
CouchRest::Model - ORM, the CouchDB way
CouchRest::Model provides an ORM-like interface for CouchDB documents. It avoids all usage of method_missing, and tries to strike a balance between usability and magic. See CouchRest::Model#view_by for documentation about the view-generation system.
Example
This is an example class using CouchRest::Model. It is taken from the spec/couchrest/core/model_spec.rb file, which may be even more up to date than this example.
class Article < CouchRest::Model
use_database CouchRest.database!('http://localhost:5984/couchrest-model-test')
unique_id :slug
view_by :date, :descending => true
view_by :user_id, :date
view_by :tags,
:map =>
"function(doc) {
if (doc['couchrest-type'] == 'Article' && doc.tags) {
doc.tags.forEach(function(tag){
emit(tag, 1);
});
}
}",
:reduce =>
"function(keys, values, rereduce) {
return sum(values);
}"
key_writer :date
key_reader :slug, :created_at, :updated_at
key_accessor :title, :tags
before(:create, :generate_slug_from_title)
def generate_slug_from_title
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'')
end
end
Class Method Summary collapse
- .all(opts = {}) ⇒ Object
-
.cast(field, opts = {}) ⇒ Object
Cast a field as another class.
-
.database ⇒ Object
returns the CouchRest::Database instance that this class uses.
- .default ⇒ Object
-
.design_doc ⇒ Object
Fetch the generated design doc.
-
.get(id) ⇒ Object
load a document from the database.
-
.key_accessor(*keys) ⇒ Object
Defines methods for reading and writing from fields in the document.
-
.key_reader(*keys) ⇒ Object
For each argument key, define a method
keythat reads the corresponding field on the CouchDB document. -
.key_writer(*keys) ⇒ Object
For each argument key, define a method
key=that sets the corresponding field on the CouchDB document. - .set_default(hash) ⇒ Object
-
.timestamps! ⇒ Object
Automatically set
updated_atandcreated_atfields on the document whenever saving occurs. -
.unique_id(method = nil, &block) ⇒ Object
Name a method that will be called before the document is first saved, which returns a string to be used for the document’s
_id. -
.use_database(db) ⇒ Object
override the CouchRest::Model-wide default_database.
-
.view_by(*keys) ⇒ Object
Define a CouchDB view.
Instance Method Summary collapse
-
#database ⇒ Object
returns the database used by this model’s class.
-
#destroy ⇒ Object
Deletes the document from the database.
-
#id ⇒ Object
alias for self.
-
#initialize(keys = {}) ⇒ Model
constructor
instantiates the hash by converting all the keys to strings.
-
#new_record? ⇒ Boolean
returns true if the document has never been saved.
-
#rev ⇒ Object
alias for self.
-
#save ⇒ Object
Saves the document to the db using create or update.
Constructor Details
#initialize(keys = {}) ⇒ Model
instantiates the hash by converting all the keys to strings.
55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/couchrest/core/model.rb', line 55 def initialize keys = {} super() apply_defaults keys.each do |k,v| self[k.to_s] = v end cast_keys unless self['_id'] && self['_rev'] self['couchrest-type'] = self.class.to_s end end |
Class Method Details
.all(opts = {}) ⇒ Object
95 96 97 98 99 100 101 102 103 104 |
# File 'lib/couchrest/core/model.rb', line 95 def all opts = {} self.generated_design_doc ||= default_design_doc unless design_doc_fresh refresh_design_doc end view_name = "#{design_doc_slug}/all" raw = opts.delete(:raw) view = fetch_view(view_name, opts) process_view_results view, raw end |
.cast(field, opts = {}) ⇒ Object
Cast a field as another class. The class must be happy to have the field’s primitive type as the argument to it’s constucture. Classes which inherit from CouchRest::Model are happy to act as sub-objects for any fields that are stored in JSON as object (and therefore are parsed from the JSON as Ruby Hashes).
111 112 113 114 |
# File 'lib/couchrest/core/model.rb', line 111 def cast field, opts = {} self.casts ||= {} self.casts[field.to_s] = opts end |
.database ⇒ Object
returns the CouchRest::Database instance that this class uses
85 86 87 |
# File 'lib/couchrest/core/model.rb', line 85 def database self.class_database || CouchRest::Model.default_database end |
.default ⇒ Object
145 146 147 |
# File 'lib/couchrest/core/model.rb', line 145 def default self.default_obj end |
.design_doc ⇒ Object
Fetch the generated design doc. Could raise an error if the generated views have not been queried yet.
298 299 300 |
# File 'lib/couchrest/core/model.rb', line 298 def design_doc database.get("_design/#{design_doc_slug}") end |
.get(id) ⇒ Object
load a document from the database
90 91 92 93 |
# File 'lib/couchrest/core/model.rb', line 90 def get id doc = database.get id new(doc) end |
.key_accessor(*keys) ⇒ Object
Defines methods for reading and writing from fields in the document. Uses key_writer and key_reader internally.
118 119 120 121 |
# File 'lib/couchrest/core/model.rb', line 118 def key_accessor *keys key_writer *keys key_reader *keys end |
.key_reader(*keys) ⇒ Object
For each argument key, define a method key that reads the corresponding field on the CouchDB document.
136 137 138 139 140 141 142 143 |
# File 'lib/couchrest/core/model.rb', line 136 def key_reader *keys keys.each do |method| key = method.to_s define_method method do self[key] end end end |
.key_writer(*keys) ⇒ Object
For each argument key, define a method key= that sets the corresponding field on the CouchDB document.
125 126 127 128 129 130 131 132 |
# File 'lib/couchrest/core/model.rb', line 125 def key_writer *keys keys.each do |method| key = method.to_s define_method "#{method}=" do |value| self[key] = value end end end |
.set_default(hash) ⇒ Object
149 150 151 |
# File 'lib/couchrest/core/model.rb', line 149 def set_default hash self.default_obj = hash end |
.timestamps! ⇒ Object
Automatically set updated_at and created_at fields on the document whenever saving occurs. CouchRest uses a pretty decent time format by default. See Time#to_json
156 157 158 159 160 161 162 163 |
# File 'lib/couchrest/core/model.rb', line 156 def before(:create) do self['updated_at'] = self['created_at'] = Time.now end before(:update) do self['updated_at'] = Time.now end end |
.unique_id(method = nil, &block) ⇒ Object
Name a method that will be called before the document is first saved, which returns a string to be used for the document’s _id. Because CouchDB enforces a constraint that each id must be unique, this can be used to enforce eg: uniq usernames. Note that this id must be globally unique across all document types which share a database, so if you’d like to scope uniqueness to this class, you should use the class name as part of the unique id.
172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/couchrest/core/model.rb', line 172 def unique_id method = nil, &block if method define_method :set_unique_id do self['_id'] ||= self.send(method) end elsif block define_method :set_unique_id do uniqid = block.call(self) raise ArgumentError, "unique_id block must not return nil" if uniqid.nil? self['_id'] ||= uniqid end end end |
.use_database(db) ⇒ Object
override the CouchRest::Model-wide default_database
80 81 82 |
# File 'lib/couchrest/core/model.rb', line 80 def use_database db self.class_database = db end |
.view_by(*keys) ⇒ Object
Define a CouchDB view. The name of the view will be the concatenation of by and the keys joined by and
Example views:
class Post
# view with default options
# query with Post.by_date
view_by :date, :descending => true
# view with compound sort-keys
# query with Post.by_user_id_and_date
view_by :user_id, :date
# view with custom map/reduce functions
# query with Post.by_tags :reduce => true
view_by :tags,
:map =>
"function(doc) {
if (doc['couchrest-type'] == 'Post' && doc.tags) {
doc.tags.forEach(function(tag){
emit(doc.tag, 1);
});
}
}",
:reduce =>
"function(keys, values, rereduce) {
return sum(values);
}"
end
view_by :date will create a view defined by this Javascript function:
function(doc) {
if (doc['couchrest-type'] == 'Post' && doc.date) {
emit(doc.date, null);
}
}
It can be queried by calling Post.by_date which accepts all valid options for CouchRest::Database#view. In addition, calling with the :raw => true option will return the view rows themselves. By default Post.by_date will return the documents included in the generated view.
CouchRest::Database#view options can be applied at view definition time as defaults, and they will be curried and used at view query time. Or they can be overridden at query time.
Custom views can be queried with :reduce => true to return reduce results. The default for custom views is to query with :reduce => false.
Views are generated (on a per-model basis) lazily on first-access. This means that if you are deploying changes to a view, the views for that model won’t be available until generation is complete. This can take some time with large databases. Strategies are in the works.
To understand the capabilities of this view system more compeletly, it is recommended that you read the RSpec file at spec/core/model.rb.
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/couchrest/core/model.rb', line 248 def view_by *keys opts = keys.pop if keys.last.is_a?(Hash) opts ||= {} type = self.to_s method_name = "by_#{keys.join('_and_')}" self.generated_design_doc ||= default_design_doc if opts[:map] view = {} view['map'] = opts.delete(:map) if opts[:reduce] view['reduce'] = opts.delete(:reduce) opts[:reduce] = false end generated_design_doc['views'][method_name] = view else doc_keys = keys.collect{|k|"doc['#{k}']"} key_protection = doc_keys.join(' && ') key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]" map_function = <<-JAVASCRIPT function(doc) { if (doc['couchrest-type'] == '#{type}' && #{key_protection}) { emit(#{key_emit}, null); } } JAVASCRIPT generated_design_doc['views'][method_name] = { 'map' => map_function } end self.design_doc_fresh = false self..instance_eval do define_method method_name do |*args| query = opts.merge(args[0] || {}) query[:raw] = true if query[:reduce] unless design_doc_fresh refresh_design_doc end raw = query.delete(:raw) view_name = "#{design_doc_slug}/#{method_name}" view = fetch_view(view_name, query) process_view_results view, raw end end end |
Instance Method Details
#database ⇒ Object
returns the database used by this model’s class
372 373 374 |
# File 'lib/couchrest/core/model.rb', line 372 def database self.class.database end |
#destroy ⇒ Object
Deletes the document from the database. Runs the :delete callbacks. Removes the _id and _rev fields, preparing the document to be saved to a new _id.
405 406 407 408 409 410 411 412 |
# File 'lib/couchrest/core/model.rb', line 405 def destroy result = database.delete self if result['ok'] self['_rev'] = nil self['_id'] = nil end result['ok'] end |
#id ⇒ Object
alias for self
377 378 379 |
# File 'lib/couchrest/core/model.rb', line 377 def id self['_id'] end |
#new_record? ⇒ Boolean
returns true if the document has never been saved
387 388 389 |
# File 'lib/couchrest/core/model.rb', line 387 def new_record? !rev end |
#rev ⇒ Object
alias for self
382 383 384 |
# File 'lib/couchrest/core/model.rb', line 382 def rev self['_rev'] end |
#save ⇒ Object
Saves the document to the db using create or update. Also runs the :save callbacks. Sets the _id and _rev fields based on CouchDB’s response.
394 395 396 397 398 399 400 |
# File 'lib/couchrest/core/model.rb', line 394 def save if new_record? create else update end end |