diff --git a/lib/couchrest/core/model.rb b/lib/couchrest/core/model.rb
index 811d0fd..3c6d4da 100644
--- a/lib/couchrest/core/model.rb
+++ b/lib/couchrest/core/model.rb
@@ -62,268 +62,272 @@ module CouchRest
init_doc
end
end
-
+
class << self
# this is the CouchRest::Database that model classes will use unless
# they override it with use_database
attr_accessor :default_database
attr_accessor :template
-
+
# override the CouchRest::Model-wide default_database
- def use_database db
- @database = db
- end
+ def use_database db
+ @database = db
+ end
- # returns the CouchRest::Database instance that this class uses
- def database
- @database || CouchRest::Model.default_database
- end
+ # returns the CouchRest::Database instance that this class uses
+ def database
+ @database || CouchRest::Model.default_database
+ end
- # load a document from the database
- def get id
- doc = database.get id
- new(doc)
- end
+ # load a document from the database
+ def get id
+ doc = database.get id
+ new(doc)
+ end
- # Defines methods for reading and writing from fields in the document.
- # Uses key_writer and key_reader internally.
- def key_accessor *keys
- key_writer *keys
- key_reader *keys
- end
+ def cast field, opts = {}
+
+ end
- # For each argument key, define a method key= that sets the
- # corresponding field on the CouchDB document.
- def key_writer *keys
- keys.each do |method|
- key = method.to_s
- define_method "#{method}=" do |value|
- self[key] = value
- end
- end
- end
+ # Defines methods for reading and writing from fields in the document.
+ # Uses key_writer and key_reader internally.
+ def key_accessor *keys
+ key_writer *keys
+ key_reader *keys
+ end
- # For each argument key, define a method key that reads the
- # corresponding field on the CouchDB document.
- def key_reader *keys
- keys.each do |method|
- key = method.to_s
- define_method method do
- self[key]
- end
- end
- end
-
- def default
- @default
- end
-
- def set_default hash
- @default = hash
- end
+ # For each argument key, define a method key= that sets the
+ # corresponding field on the CouchDB document.
+ def key_writer *keys
+ keys.each do |method|
+ key = method.to_s
+ define_method "#{method}=" do |value|
+ self[key] = value
+ end
+ end
+ end
- # 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
- def timestamps!
- before(:create) do
- self['updated_at'] = self['created_at'] = Time.now
- end
- before(:update) do
- self['updated_at'] = Time.now
- end
- end
+ # For each argument key, define a method key that reads the
+ # corresponding field on the CouchDB document.
+ def key_reader *keys
+ keys.each do |method|
+ key = method.to_s
+ define_method method do
+ self[key]
+ end
+ end
+ end
- # 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.
- 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
-
- # 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.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.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.
- def view_by *keys
- opts = keys.pop if keys.last.is_a?(Hash)
- opts ||= {}
- type = self.to_s
+ def default
+ @default
+ end
- method_name = "by_#{keys.join('_and_')}"
- @@design_doc ||= default_design_doc
+ def set_default hash
+ @default = hash
+ end
- if opts[:map]
- view = {}
- view['map'] = opts.delete(:map)
- if opts[:reduce]
- view['reduce'] = opts.delete(:reduce)
- opts[:reduce] = false
- end
- @@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.type == '#{type}' && #{key_protection}) {
- emit(#{key_emit}, null);
- }
- }
- JAVASCRIPT
- @@design_doc['views'][method_name] = {
- 'map' => map_function
- }
- end
+ # 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
+ def timestamps!
+ before(:create) do
+ self['updated_at'] = self['created_at'] = Time.now
+ end
+ before(:update) do
+ self['updated_at'] = Time.now
+ end
+ end
- @@design_doc_fresh = false
+ # 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.
+ 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
- self.meta_class.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 = "#{type}/#{method_name}"
+ # 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.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.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.
+ def view_by *keys
+ opts = keys.pop if keys.last.is_a?(Hash)
+ opts ||= {}
+ type = self.to_s
- view = fetch_view(view_name, query)
- if raw
- view
- else
- # TODO this can be optimized once the include-docs patch is applied
- view['rows'].collect{|r|new(database.get(r['id']))}
- end
- end
- end
- end
+ method_name = "by_#{keys.join('_and_')}"
+ @@design_doc ||= default_design_doc
- private
+ if opts[:map]
+ view = {}
+ view['map'] = opts.delete(:map)
+ if opts[:reduce]
+ view['reduce'] = opts.delete(:reduce)
+ opts[:reduce] = false
+ end
+ @@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.type == '#{type}' && #{key_protection}) {
+ emit(#{key_emit}, null);
+ }
+ }
+ JAVASCRIPT
+ @@design_doc['views'][method_name] = {
+ 'map' => map_function
+ }
+ end
- def fetch_view view_name, opts
- retryable = true
- begin
- database.view(view_name, opts)
- # the design doc could have been deleted by a rouge process
- rescue RestClient::ResourceNotFound => e
- if retryable
- refresh_design_doc
- retryable = false
- retry
- else
- raise e
- end
- end
- end
+ @@design_doc_fresh = false
- def design_doc_id
- "_design/#{self.to_s}"
- end
+ self.meta_class.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 = "#{type}/#{method_name}"
- def default_design_doc
- {
- "_id" => design_doc_id,
- "language" => "javascript",
- "views" => {}
- }
- end
+ view = fetch_view(view_name, query)
+ if raw
+ view
+ else
+ # TODO this can be optimized once the include-docs patch is applied
+ view['rows'].collect{|r|new(database.get(r['id']))}
+ end
+ end
+ end
+ end
- def refresh_design_doc
- saved = database.get(design_doc_id) rescue nil
- if saved
- @@design_doc['views'].each do |name, view|
- saved['views'][name] = view
- end
- database.save(saved)
- else
- database.save(@@design_doc)
- end
- @@design_doc_fresh = true
- end
+ private
+
+ def fetch_view view_name, opts
+ retryable = true
+ begin
+ database.view(view_name, opts)
+ # the design doc could have been deleted by a rouge process
+ rescue RestClient::ResourceNotFound => e
+ if retryable
+ refresh_design_doc
+ retryable = false
+ retry
+ else
+ raise e
+ end
+ end
+ end
+
+ def design_doc_id
+ "_design/#{self.to_s}"
+ end
+
+ def default_design_doc
+ {
+ "_id" => design_doc_id,
+ "language" => "javascript",
+ "views" => {}
+ }
+ end
+
+ def refresh_design_doc
+ saved = database.get(design_doc_id) rescue nil
+ if saved
+ @@design_doc['views'].each do |name, view|
+ saved['views'][name] = view
+ end
+ database.save(saved)
+ else
+ database.save(@@design_doc)
+ end
+ @@design_doc_fresh = true
+ end
end # class << self
-
-
+
+
# returns the database used by this model's class
def database
self.class.database
end
-
+
# alias for self['_id']
def id
self['_id']
@@ -333,12 +337,12 @@ module CouchRest
def rev
self['_rev']
end
-
+
# returns true if the document has never been saved
def new_record?
!rev
end
-
+
# 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.
@@ -363,21 +367,21 @@ module CouchRest
end
protected
-
+
# Saves a document for the first time, after running the before(:create)
# callbacks, and applying the unique_id.
def create
set_unique_id if respond_to?(:set_unique_id) # hack
save_doc
end
-
+
# Saves the document and runs the :update callbacks.
def update
save_doc
end
-
+
private
-
+
def save_doc
result = database.save self
if result['ok']
@@ -386,13 +390,13 @@ module CouchRest
end
result['ok']
end
-
+
def init_doc
self['type'] = self.class.to_s
end
-
+
include ::Extlib::Hook
register_instance_hooks :save, :create, :update, :destroy
-
+
end # class Model
end # module CouchRest
\ No newline at end of file