diff --git a/README.md b/README.md
index 537e86d..cc98d59 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,25 @@
-# CouchRest::ExtendedDocument: CouchDB, not too close to the metal
+# CouchRest Model: CouchDB, close to shiny metal with rounded edges
-CouchRest::ExtendedDocument adds additional functionality to the standard CouchRest Document class such as
+CouchRest Models adds additional functionality to the standard CouchRest Document class such as
setting properties, callbacks, typecasting, and validations.
-Note: CouchRest::ExtendedDocument only supports CouchDB 0.10.0 or newer.
+Originally called ExtendedDocument, the new Model structure uses ActiveModel, part of Rails 3,
+for validations and callbacks.
+
+If your project is still running Rails 2.3, you'll have to continue using ExtendedDocument as
+it is not possible to load ActiveModel into programs that do not use ActiveSupport 3.0.
+
+CouchRest Model only supports CouchDB 0.10.0 or newer.
## Install
- $ sudo gem install couchrest_extended_document
+ $ sudo gem install couchrest_model
-## Usage
+## General Usage
-### General
+ require 'couchrest_model'
- require 'couchrest_extended_document'
-
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :name, String
property :lives, Integer, :default => 9
@@ -43,9 +47,9 @@ Note: CouchRest::ExtendedDocument only supports CouchDB 0.10.0 or newer.
@cat.random_text # Raises error!
-### Properties
+## Properties
-Only attributes with a property definition will be stored be ExtendedDocument (as opposed
+Only attributes with a property definition will be stored be CouchRest Model (as opposed
to a normal CouchRest Document which will store everything). To help prevent confusion,
a property should be considered as the definition of an attribute. An attribute must be associated
with a property, but a property may not have any attributes associated if none have been set.
@@ -56,7 +60,7 @@ will only create a getter and setter passing all attribute data directly to the
provided responds to +to_json+, there will not be any problems saving it, but when loading the
data back it will either be a string, number, array, or hash:
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :name
property :birthday
end
@@ -82,7 +86,7 @@ Properties create getters and setters similar to the following:
Properties can also have a type which
will be used for casting data retrieved from CouchDB when the attribute is set:
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :name, String
property :last_fed_at, Time
end
@@ -96,7 +100,7 @@ will be used for casting data retrieved from CouchDB when the attribute is set:
Booleans or TrueClass will also create a getter with question mark at the end:
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :awake, TrueClass, :default => true
end
@@ -108,7 +112,7 @@ Defining a property as read-only will mean that its value is set only when read
database and that it will not have a setter method. You can however update a read-only
attribute using the +write_attribute+ method:
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :name, String
property :lives, Integer, :default => 9, :readonly => true
@@ -123,12 +127,12 @@ attribute using the +write_attribute+ method:
@cat.lives # Now 8!
-### Property Arrays
+## Property Arrays
-An attribute may also contain an array of data. ExtendedDocument handles this, along
+An attribute may also contain an array of data. CouchRest Model handles this, along
with casting, by defining the class of the child attributes inside an Array:
- class Cat < CouchRest::ExtendedDocument
+ class Cat < CouchRest::Model::Base
property :name, String
property :nicknames, [String]
end
@@ -144,21 +148,21 @@ When anything other than a string is set as the class of a property, the array w
into special wrapper called a CastedArray. If the child objects respond to the 'casted_by' method
(such as those created with CastedModel, below) it will contain a reference to the parent.
-### Casted Models
+## Casted Models
-ExtendedDocument allows you to take full advantage of CouchDB's ability to store complex
+CouchRest Model allows you to take full advantage of CouchDB's ability to store complex
documents and retrieve them using the CastedModel module. Simply include the module in
a Hash (or other model that responds to the [] and []= methods) and set any properties
you'd like to use. For example:
class CatToy << Hash
- include CouchRest::CastedModel
+ include CouchRest::Model::CastedModel
property :name, String
property :purchased, Date
end
- class Cat << CouchRest::ExtendedDocument
+ class Cat << CouchRest::Model::Base
property :name, String
property :toys, [CatToy]
end
@@ -174,10 +178,10 @@ Additionally, any hashes sent to the property will automatically be converted:
Of course, to use your own classes they *must* be defined before the parent uses them otherwise
Ruby will bring up a missing constant error. To avoid this, or if you have a really simple array of data
-you'd like to model, the latest version of ExtendedDocument (> 1.0.0) supports creating
+you'd like to model, the latest version of CouchRest Model (> 1.0.0) supports creating
anonymous classes:
- class Cat << CouchRest::ExtendedDocument
+ class Cat << CouchRest::Model::Base
property :name, String
property :toys do |toy|
@@ -192,26 +196,34 @@ anonymous classes:
Using this method of anonymous classes will *only* create arrays of objects.
-### Notable Issues
+## Notable Issues
-ExtendedDocument uses active_support for some of its internals. Ensure you have a stable active support gem installed
+CouchRest Model uses active_support for some of its internals. Ensure you have a stable active support gem installed
or at least 3.0.0.beta4.
JSON gem versions 1.4.X are kown to cause problems with stack overflows and general badness. Version 1.2.4 appears to work fine.
-### Ruby on Rails
+## Ruby on Rails
-CouchRest::ExtendedDocument is compatible with rails and provides some ActiveRecord-like methods.
-You might also be interested in the CouchRest companion rails project:
-[http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
+CouchRest Model is compatible with rails and provides some ActiveRecord-like methods.
-#### Rails 2.X
+The CouchRest companion rails project
+[http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails) is great
+for provided default connection details for your database. At the time of writting however it
+does not provide explicit support for CouchRest Model.
+
+CouchRest Model and the original CouchRest ExtendedDocument do not share the same namespace,
+as such you should not have any problems using them both at the same time. This might
+help with migrations.
+
+
+### Rails 3.0
In your environment.rb file require the gem as follows:
Rails::Initializer.run do |config|
....
- config.gem "couchrest_extended_document"
+ config.gem "couchrest_model"
....
end
@@ -223,12 +235,11 @@ CouchRest install, from the project root directory run `rake`, or `autotest`
## Docs
-API: [http://rdoc.info/projects/couchrest/couchrest_extended_document](http://rdoc.info/projects/couchrest/couchrest_extended_document)
+API: [http://rdoc.info/projects/couchrest/couchrest_model](http://rdoc.info/projects/couchrest/couchrest_model)
Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
-
## Contact
Please post bugs, suggestions and patches to the bug tracker at [http://github.com/couchrest/couchrest/issues](http://github.com/couchrest/couchrest/issues).
diff --git a/Rakefile b/Rakefile
index c296968..abae8b9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,8 @@
require 'rake'
require "rake/rdoctask"
-require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest_extended_document')
+
+$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
+require 'couchrest_model'
begin
require 'spec/rake/spectask'
@@ -15,19 +17,20 @@ end
begin
require 'jeweler'
Jeweler::Tasks.new do |gemspec|
- gemspec.name = "couchrest_extended_document"
- gemspec.summary = "Extend CouchRest Document class with useful features."
- gemspec.description = "CouchRest::ExtendedDocument provides aditional features to the standard CouchRest::Document class such as properties, view designs, callbacks, typecasting and validations."
+ gemspec.name = "couchrest_model"
+ gemspec.summary = "Extends the CouchRest Document for advanced modelling."
+ gemspec.description = "CouchRest Model provides aditional features to the standard CouchRest Document class such as properties, view designs, associations, callbacks, typecasting and validations."
gemspec.email = "jchris@apache.org"
- gemspec.homepage = "http://github.com/couchrest/couchrest_extended_document"
- gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
+ gemspec.homepage = "http://github.com/couchrest/couchrest_model"
+ gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
gemspec.has_rdoc = true
gemspec.add_dependency("couchrest", ">= 1.0.0.beta")
gemspec.add_dependency("mime-types", ">= 1.15")
gemspec.add_dependency("activesupport", ">= 2.3.5")
- gemspec.version = CouchRest::ExtendedDocument::VERSION
+ gemspec.add_dependency("activemodel", ">= 3.0.0.beta4")
+ gemspec.version = CouchRest::Model::VERSION
gemspec.date = "2008-11-22"
gemspec.require_path = "lib"
end
diff --git a/history.txt b/history.txt
index f385ebd..9001ce1 100644
--- a/history.txt
+++ b/history.txt
@@ -4,8 +4,22 @@
* Minor enhancements
+== CouchRest Model 1.0.0.beta7
+
+* Major enhancements
+ * Renamed ExtendedDocument to CouchRest::Model
+ * Added initial support for simple belongs_to associations
+ * Added support for basic collection_of association (unique to document databases!)
+ * Moved Validation to ActiveModel
+ * Moved Callbacks to ActiveModel
+ * Removed support for properties defined using a string for the type instead of a class
+
+
== 1.0.0.beta6
+* Major enhancements
+ * Added support for anonymous CastedModels defined in Documents
+
* Minor enhancements
* Added 'find_by_*' alias for finding first item in view with matching key.
* Fixed issue with active_support in Rails3 and text in README for JSON.
@@ -15,11 +29,6 @@
* Setting a property of type Array (or keyed hash) must be an array or an error will be raised.
* Now possible to set Array attribute from hash where keys determine order.
-* Major enhancements
- * Added support for anonymous CastedModels defined in Documents
- * Added initial support for simple belongs_to associations
- * Added support for basic collection_of association (unique to document databases!)
-
== 1.0.0.beta5
* Minor enhancements
diff --git a/lib/couchrest/extended_document.rb b/lib/couchrest/extended_document.rb
deleted file mode 100644
index 84c86d1..0000000
--- a/lib/couchrest/extended_document.rb
+++ /dev/null
@@ -1,261 +0,0 @@
-
-require File.join(File.dirname(__FILE__), "property")
-require File.join(File.dirname(__FILE__), "validation")
-require File.join(File.dirname(__FILE__), 'mixins')
-
-module CouchRest
-
- # Same as CouchRest::Document but with properties and validations
- class ExtendedDocument < Document
-
- VERSION = "1.0.0.beta6"
-
- include CouchRest::Mixins::Callbacks
- include CouchRest::Mixins::DocumentQueries
- include CouchRest::Mixins::Views
- include CouchRest::Mixins::DesignDoc
- include CouchRest::Mixins::ExtendedAttachments
- include CouchRest::Mixins::ClassProxy
- include CouchRest::Mixins::Collection
- include CouchRest::Mixins::AttributeProtection
- include CouchRest::Mixins::Attributes
- include CouchRest::Mixins::Associations
-
- # Including validation here does not work due to the way inheritance is handled.
- #include CouchRest::Validation
-
- def self.subclasses
- @subclasses ||= []
- end
-
- def self.inherited(subklass)
- super
- subklass.send(:include, CouchRest::Mixins::Properties)
- subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
- def self.inherited(subklass)
- super
- subklass.properties = self.properties.dup
- end
- EOS
- subclasses << subklass
- end
-
- # Accessors
- attr_accessor :casted_by
-
- # Callbacks
- define_callbacks :create, "result == :halt"
- define_callbacks :save, "result == :halt"
- define_callbacks :update, "result == :halt"
- define_callbacks :destroy, "result == :halt"
-
- # Creates a new instance, bypassing attribute protection
- #
- #
- # ==== Returns
- # a document instance
- def self.create_from_database(doc = {})
- base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
- base.new(doc, :directly_set_attributes => true)
- end
-
-
- # Instantiate a new ExtendedDocument by preparing all properties
- # using the provided document hash.
- #
- # Options supported:
- #
- # * :directly_set_attributes: true when data comes directly from database
- #
- def initialize(doc = {}, options = {})
- prepare_all_attributes(doc, options) # defined in CouchRest::Mixins::Attributes
- super(doc)
- unless self['_id'] && self['_rev']
- self['couchrest-type'] = self.class.to_s
- end
- after_initialize if respond_to?(:after_initialize)
- end
-
- # Defines an instance and save it directly to the database
- #
- # ==== Returns
- # returns the reloaded document
- def self.create(options)
- instance = new(options)
- instance.create
- instance
- end
-
- # Defines an instance and save it directly to the database
- #
- # ==== Returns
- # returns the reloaded document or raises an exception
- def self.create!(options)
- instance = new(options)
- instance.create!
- instance
- 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 self.timestamps!
- class_eval <<-EOS, __FILE__, __LINE__
- property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
- property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
-
- set_callback :save, :before do |object|
- write_attribute('updated_at', Time.now)
- write_attribute('created_at', Time.now) if object.new?
- end
- EOS
- 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 self.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
-
- # Temp solution to make the view_by methods available
- def self.method_missing(m, *args, &block)
- if has_view?(m)
- query = args.shift || {}
- return view(m, query, *args, &block)
- elsif m.to_s =~ /^find_(by_.+)/
- view_name = $1
- if has_view?(view_name)
- return first_from_view(view_name, *args)
- end
- end
- super
- end
-
- ### instance methods
-
- # Gets a reference to the actual document in the DB
- # Calls up to the next document if there is one,
- # Otherwise we're at the top and we return self
- def base_doc
- return self if base_doc?
- @casted_by.base_doc
- end
-
- # Checks if we're the top document
- def base_doc?
- !@casted_by
- end
-
- # for compatibility with old-school frameworks
- alias :new_record? :new?
- alias :new_document? :new?
-
- # Trigger the callbacks (before, after, around)
- # and create the document
- # It's important to have a create callback since you can't check if a document
- # was new after you saved it
- #
- # When creating a document, both the create and the save callbacks will be triggered.
- def create(bulk = false)
- caught = catch(:halt) do
- _run_create_callbacks do
- _run_save_callbacks do
- create_without_callbacks(bulk)
- end
- end
- end
- end
-
- # unlike save, create returns the newly created document
- def create_without_callbacks(bulk =false)
- raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
- set_unique_id if new? && self.respond_to?(:set_unique_id)
- result = database.save_doc(self, bulk)
- (result["ok"] == true) ? self : false
- end
-
- # Creates the document in the db. Raises an exception
- # if the document is not created properly.
- def create!
- raise "#{self.inspect} failed to save" unless self.create
- end
-
- # Trigger the callbacks (before, after, around)
- # only if the document isn't new
- def update(bulk = false)
- caught = catch(:halt) do
- if self.new?
- save(bulk)
- else
- _run_update_callbacks do
- _run_save_callbacks do
- save_without_callbacks(bulk)
- end
- end
- end
- end
- end
-
- # Trigger the callbacks (before, after, around)
- # and save the document
- def save(bulk = false)
- caught = catch(:halt) do
- if self.new?
- _run_save_callbacks do
- save_without_callbacks(bulk)
- end
- else
- update(bulk)
- end
- end
- end
-
- # Overridden to set the unique ID.
- # Returns a boolean value
- def save_without_callbacks(bulk = false)
- raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
- set_unique_id if new? && self.respond_to?(:set_unique_id)
- result = database.save_doc(self, bulk)
- result["ok"] == true
- end
-
- # Saves the document to the db using save. Raises an exception
- # if the document is not saved properly.
- def save!
- raise "#{self.inspect} failed to save" unless self.save
- true
- end
-
- # Deletes the document from the database. Runs the :destroy callbacks.
- # Removes the _id and _rev fields, preparing the
- # document to be saved to a new _id.
- def destroy(bulk=false)
- caught = catch(:halt) do
- _run_destroy_callbacks do
- result = database.delete_doc(self, bulk)
- if result['ok']
- self.delete('_rev')
- self.delete('_id')
- end
- result['ok']
- end
- end
- end
-
- end
-end
diff --git a/lib/couchrest/mixins.rb b/lib/couchrest/mixins.rb
deleted file mode 100644
index 509e1a6..0000000
--- a/lib/couchrest/mixins.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
-
-require File.join(mixins_dir, 'callbacks')
-require File.join(mixins_dir, 'properties')
-require File.join(mixins_dir, 'document_queries')
-require File.join(mixins_dir, 'views')
-require File.join(mixins_dir, 'design_doc')
-require File.join(mixins_dir, 'extended_attachments')
-require File.join(mixins_dir, 'class_proxy')
-require File.join(mixins_dir, 'collection')
-require File.join(mixins_dir, 'attribute_protection')
-require File.join(mixins_dir, 'attributes')
-require File.join(mixins_dir, 'associations')
diff --git a/lib/couchrest/mixins/callbacks.rb b/lib/couchrest/mixins/callbacks.rb
deleted file mode 100644
index 989fe25..0000000
--- a/lib/couchrest/mixins/callbacks.rb
+++ /dev/null
@@ -1,532 +0,0 @@
-# Copyright (c) 2006-2009 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#
-# Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
-# http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
-# http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
-
-module CouchRest
- module Mixins
- # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
- # before or after an alteration of the object state.
- #
- # Mixing in this module allows you to define callbacks in your class.
- #
- # Example:
- # class Storage
- # include ActiveSupport::Callbacks
- #
- # define_callbacks :save
- # end
- #
- # class ConfigStorage < Storage
- # save_callback :before, :saving_message
- # def saving_message
- # puts "saving..."
- # end
- #
- # save_callback :after do |object|
- # puts "saved"
- # end
- #
- # def save
- # _run_save_callbacks do
- # puts "- save"
- # end
- # end
- # end
- #
- # config = ConfigStorage.new
- # config.save
- #
- # Output:
- # saving...
- # - save
- # saved
- #
- # Callbacks from parent classes are inherited.
- #
- # Example:
- # class Storage
- # include ActiveSupport::Callbacks
- #
- # define_callbacks :save
- #
- # save_callback :before, :prepare
- # def prepare
- # puts "preparing save"
- # end
- # end
- #
- # class ConfigStorage < Storage
- # save_callback :before, :saving_message
- # def saving_message
- # puts "saving..."
- # end
- #
- # save_callback :after do |object|
- # puts "saved"
- # end
- #
- # def save
- # _run_save_callbacks do
- # puts "- save"
- # end
- # end
- # end
- #
- # config = ConfigStorage.new
- # config.save
- #
- # Output:
- # preparing save
- # saving...
- # - save
- # saved
- module Callbacks
- def self.included(klass)
- klass.extend ClassMethods
- end
-
- def run_callbacks(kind, options = {}, &blk)
- send("_run_#{kind}_callbacks", &blk)
- end
-
- class Callback
- @@_callback_sequence = 0
-
- attr_accessor :filter, :kind, :name, :options, :per_key, :klass
- def initialize(filter, kind, options, klass)
- @kind, @klass = kind, klass
-
- normalize_options!(options)
-
- @per_key = options.delete(:per_key)
- @raw_filter, @options = filter, options
- @filter = _compile_filter(filter)
- @compiled_options = _compile_options(options)
- @callback_id = next_id
-
- _compile_per_key_options
- end
-
- def clone(klass)
- obj = super()
- obj.klass = klass
- obj.per_key = @per_key.dup
- obj.options = @options.dup
- obj.per_key[:if] = @per_key[:if].dup
- obj.per_key[:unless] = @per_key[:unless].dup
- obj.options[:if] = @options[:if].dup
- obj.options[:unless] = @options[:unless].dup
- obj
- end
-
- def normalize_options!(options)
- options[:if] = Array.wrap(options[:if])
- options[:unless] = Array.wrap(options[:unless])
-
- options[:per_key] ||= {}
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
- end
-
- def next_id
- @@_callback_sequence += 1
- end
-
- def matches?(_kind, _filter)
- @kind == _kind &&
- @filter == _filter
- end
-
- def _update_filter(filter_options, new_options)
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
- end
-
- def recompile!(_options, _per_key)
- _update_filter(self.options, _options)
- _update_filter(self.per_key, _per_key)
-
- @callback_id = next_id
- @filter = _compile_filter(@raw_filter)
- @compiled_options = _compile_options(@options)
- _compile_per_key_options
- end
-
- def _compile_per_key_options
- key_options = _compile_options(@per_key)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _one_time_conditions_valid_#{@callback_id}?
- true #{key_options[0]}
- end
- RUBY_EVAL
- end
-
- # This will supply contents for before and around filters, and no
- # contents for after filters (for the forward pass).
- def start(key = nil, options = {})
- object, terminator = (options || {}).values_at(:object, :terminator)
-
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- terminator ||= false
-
- # options[0] is the compiled form of supplied conditions
- # options[1] is the "end" for the conditional
-
- if @kind == :before || @kind == :around
- if @kind == :before
- # if condition # before_save :filter_name, :if => :condition
- # filter_name
- # end
- filter = <<-RUBY_EVAL
- unless halted
- result = #{@filter}
- halted = (#{terminator})
- end
- RUBY_EVAL
-
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
- else
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `around_save :filter_name, :if => :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
-
- name = "_conditional_callback_#{@kind}_#{next_id}"
- txt, line = <<-RUBY_EVAL, __LINE__ + 1
- def #{name}(halted)
- #{@compiled_options[0] || "if true"} && !halted
- #{@filter} do
- yield self
- end
- else
- yield self
- end
- end
- RUBY_EVAL
- @klass.class_eval(txt, __FILE__, line)
- "#{name}(halted) do"
- end
- end
- end
-
- # This will supply contents for around and after filters, but not
- # before filters (for the backward pass).
- def end(key = nil, options = {})
- object = (options || {})[:object]
-
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- if @kind == :around || @kind == :after
- # if condition # after_save :filter_name, :if => :condition
- # filter_name
- # end
- if @kind == :after
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
- else
- "end"
- end
- end
- end
-
- private
- # Options support the same options as filters themselves (and support
- # symbols, string, procs, and objects), so compile a conditional
- # expression based on the options
- def _compile_options(options)
- return [] if options[:if].empty? && options[:unless].empty?
-
- conditions = []
-
- unless options[:if].empty?
- conditions << Array.wrap(_compile_filter(options[:if]))
- end
-
- unless options[:unless].empty?
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
- end
-
- ["if #{conditions.flatten.join(" && ")}", "end"]
- end
-
- # Filters support:
- # Arrays:: Used in conditions. This is used to specify
- # multiple conditions. Used internally to
- # merge conditions from skip_* filters
- # Symbols:: A method to call
- # Strings:: Some content to evaluate
- # Procs:: A proc to call with the object
- # Objects:: An object with a before_foo method on it to call
- #
- # All of these objects are compiled into methods and handled
- # the same after this point:
- # Arrays:: Merged together into a single filter
- # Symbols:: Already methods
- # Strings:: class_eval'ed into methods
- # Procs:: define_method'ed into methods
- # Objects::
- # a method is created that calls the before_foo method
- # on the object.
- def _compile_filter(filter)
- method_name = "_callback_#{@kind}_#{next_id}"
- case filter
- when Array
- filter.map {|f| _compile_filter(f)}
- when Symbol
- filter
- when String
- "(#{filter})"
- when Proc
- @klass.send(:define_method, method_name, &filter)
- return method_name if filter.arity == 0
-
- method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
- else
- @klass.send(:define_method, "#{method_name}_object") { filter }
-
- _normalize_legacy_filter(kind, filter)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{method_name}(&blk)
- #{method_name}_object.send(:#{kind}, self, &blk)
- end
- RUBY_EVAL
-
- method_name
- end
- end
-
- def _normalize_legacy_filter(kind, filter)
- if !filter.respond_to?(kind) && filter.respond_to?(:filter)
- filter.class_eval(
- "def #{kind}(context, &block) filter(context, &block) end",
- __FILE__, __LINE__ - 1)
- elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
- def filter.around(context)
- should_continue = before(context)
- yield if should_continue
- after(context)
- end
- end
- end
-
- end
-
- # An Array with a compile method
- class CallbackChain < Array
- def initialize(symbol)
- @symbol = symbol
- end
-
- def compile(key = nil, options = {})
- method = []
- method << "halted = false"
- each do |callback|
- method << callback.start(key, options)
- end
- method << "yield self if block_given? && !halted"
- reverse_each do |callback|
- method << callback.end(key, options)
- end
- method.compact.join("\n")
- end
-
- def clone(klass)
- chain = CallbackChain.new(@symbol)
- chain.push(*map {|c| c.clone(klass)})
- end
- end
-
- module ClassMethods
- #CHAINS = {:before => :before, :around => :before, :after => :after}
-
- # Make the _run_save_callbacks method. The generated method takes
- # a block that it'll yield to. It'll call the before and around filters
- # in order, yield the block, and then run the after filters.
- #
- # _run_save_callbacks do
- # save
- # end
- #
- # The _run_save_callbacks method can optionally take a key, which
- # will be used to compile an optimized callback method for each
- # key. See #define_callbacks for more information.
- def _define_runner(symbol)
- body = send("_#{symbol}_callback").
- compile(nil, :terminator => send("_#{symbol}_terminator"))
-
- body, line = <<-RUBY_EVAL, __LINE__ + 1
- def _run_#{symbol}_callbacks(key = nil, &blk)
- if key
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
-
- unless respond_to?(name)
- self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
- end
-
- send(name, &blk)
- else
- #{body}
- end
- end
- RUBY_EVAL
-
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
- class_eval body, __FILE__, line
- end
-
- # This is called the first time a callback is called with a particular
- # key. It creates a new callback method for the key, calculating
- # which callbacks can be omitted because of per_key conditions.
- def _create_keyed_callback(name, kind, obj, &blk)
- @_keyed_callbacks ||= {}
- @_keyed_callbacks[name] ||= begin
- str = send("_#{kind}_callback").
- compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
-
- class_eval "def #{name}() #{str} end", __FILE__, __LINE__
-
- true
- end
- end
-
- # Define callbacks.
- #
- # Creates a _callback method that you can use to add callbacks.
- #
- # Syntax:
- # save_callback :before, :before_meth
- # save_callback :after, :after_meth, :if => :condition
- # save_callback :around {|r| stuff; yield; stuff }
- #
- # The _callback method also updates the _run__callbacks
- # method, which is the public API to run the callbacks.
- #
- # Also creates a skip__callback method that you can use to skip
- # callbacks.
- #
- # When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in ActionPack,
- # we convert :only and :except conditions into per-key conditions.
- #
- # before_filter :authenticate, :except => "index"
- # becomes
- # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
- #
- # Per-Key conditions are evaluated only once per use of a given key.
- # In the case of the above example, you would do:
- #
- # run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
- #
- # In that case, each action_name would get its own compiled callback
- # method that took into consideration the per_key conditions. This
- # is a speed improvement for ActionPack.
- def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
- type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
- options = filters.last.is_a?(Hash) ? filters.pop : {}
- filters.unshift(block) if block
-
- callbacks = send("_#{name}_callback")
- yield callbacks, type, filters, options if block_given?
-
- _define_runner(name)
- end
-
- alias_method :_reset_callbacks, :_update_callbacks
-
- def set_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
- filters.map! do |filter|
- # overrides parent class
- callbacks.delete_if {|c| c.matches?(type, filter) }
- Callback.new(filter, type, options.dup, self)
- end
-
- options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
- end
- end
-
- def skip_callback(name, *filters, &block)
- _update_callbacks(name, filters, block) do |callbacks, type, filters, options|
- filters.each do |filter|
- callbacks = send("_#{name}_callback=", callbacks.clone(self))
-
- filter = callbacks.find {|c| c.matches?(type, filter) }
-
- if filter && options.any?
- filter.recompile!(options, options[:per_key] || {})
- else
- callbacks.delete(filter)
- end
- end
- end
- end
-
- def define_callbacks(*symbols)
- terminator = symbols.pop if symbols.last.is_a?(String)
- symbols.each do |symbol|
- extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
-
- extlib_inheritable_accessor("_#{symbol}_callback") do
- CallbackChain.new(symbol)
- end
-
- _define_runner(symbol)
-
- # Define more convenient callback methods
- # set_callback(:save, :before) becomes before_save
- [:before, :after, :around].each do |filter|
- self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.#{filter}_#{symbol}(*symbols, &blk)
- _alias_callbacks(symbols, blk) do |callback, options|
- set_callback(:#{symbol}, :#{filter}, callback, options)
- end
- end
- RUBY_EVAL
- end
- end
- end
-
- def _alias_callbacks(callbacks, block)
- options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
- callbacks.push(block) if block
- callbacks.each do |callback|
- yield callback, options
- end
- end
- end
- end
- end
-end
diff --git a/lib/couchrest/model.rb b/lib/couchrest/model.rb
new file mode 100644
index 0000000..77eb586
--- /dev/null
+++ b/lib/couchrest/model.rb
@@ -0,0 +1,10 @@
+
+module CouchRest
+
+ module Model
+
+ VERSION = "1.0.0.beta7"
+
+ end
+
+end
diff --git a/lib/couchrest/mixins/associations.rb b/lib/couchrest/model/associations.rb
similarity index 90%
rename from lib/couchrest/mixins/associations.rb
rename to lib/couchrest/model/associations.rb
index 0dbc1b6..6762250 100644
--- a/lib/couchrest/mixins/associations.rb
+++ b/lib/couchrest/model/associations.rb
@@ -1,7 +1,5 @@
module CouchRest
-
-
- module Mixins
+ module Model
module Associations
# Basic support for relationships between ExtendedDocuments
@@ -47,12 +45,12 @@ module CouchRest
# collection_of :groups
#
# creates a pseudo property called "groups" which allows access
- # to a CollectionProxy object. Adding, replacing or removing entries in this
+ # to a CollectionOfProxy object. Adding, replacing or removing entries in this
# proxy will cause the matching property array, in this case "group_ids", to
# be kept in sync.
#
# Any manual changes made to the collection ids property (group_ids), unless replaced, will require
- # a reload of the CollectionProxy for the two sets of data to be in sync:
+ # a reload of the CollectionOfProxy for the two sets of data to be in sync:
#
# group_ids = ['123']
# groups == [Group.get('123')]
@@ -63,7 +61,7 @@ module CouchRest
# Of course, saving the parent record will store the collection ids as they are
# found.
#
- # The CollectionProxy supports the following array functions, anything else will cause
+ # The CollectionOfProxy supports the following array functions, anything else will cause
# a mismatch between the collection objects and collection ids:
#
# groups << obj
@@ -128,7 +126,7 @@ module CouchRest
### collection_of support methods
def create_collection_of_property_setter(attrib, property, options)
- # ensure CollectionProxy is nil, ready to be reloaded on request
+ # ensure CollectionOfProxy is nil, ready to be reloaded on request
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{options[:foreign_key]}=(value)
@#{attrib} = nil
@@ -143,7 +141,7 @@ module CouchRest
def #{attrib}(reload = false)
return @#{attrib} unless @#{attrib}.nil? or reload
ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
- @#{attrib} = ::CouchRest::CollectionProxy.new(ary, self, '#{options[:foreign_key]}')
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
end
EOS
end
@@ -151,7 +149,7 @@ module CouchRest
def create_collection_of_setter(attrib, property, options)
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{attrib}=(value)
- @#{attrib} = ::CouchRest::CollectionProxy.new(value, self, '#{options[:foreign_key]}')
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
end
EOS
end
@@ -163,7 +161,7 @@ module CouchRest
# Special proxy for a collection of items so that adding and removing
# to the list automatically updates the associated property.
- class CollectionProxy < Array
+ class CollectionOfProxy < Array
attr_accessor :property
attr_accessor :casted_by
diff --git a/lib/couchrest/mixins/attribute_protection.rb b/lib/couchrest/model/attribute_protection.rb
similarity index 99%
rename from lib/couchrest/mixins/attribute_protection.rb
rename to lib/couchrest/model/attribute_protection.rb
index b2efc53..e73ac50 100644
--- a/lib/couchrest/mixins/attribute_protection.rb
+++ b/lib/couchrest/model/attribute_protection.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module AttributeProtection
# Attribute protection from mass assignment to CouchRest properties
#
diff --git a/lib/couchrest/mixins/attributes.rb b/lib/couchrest/model/attributes.rb
similarity index 99%
rename from lib/couchrest/mixins/attributes.rb
rename to lib/couchrest/model/attributes.rb
index 8c2753a..40ddbc9 100644
--- a/lib/couchrest/mixins/attributes.rb
+++ b/lib/couchrest/model/attributes.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module Attributes
## Support for handling attributes
diff --git a/lib/couchrest/model/base.rb b/lib/couchrest/model/base.rb
new file mode 100644
index 0000000..4c4856b
--- /dev/null
+++ b/lib/couchrest/model/base.rb
@@ -0,0 +1,93 @@
+module CouchRest
+ module Model
+ class Base < Document
+
+ extend ActiveModel::Naming
+
+ include CouchRest::Model::Persistence
+ include CouchRest::Model::Callbacks
+ include CouchRest::Model::DocumentQueries
+ include CouchRest::Model::Views
+ include CouchRest::Model::DesignDoc
+ include CouchRest::Model::ExtendedAttachments
+ include CouchRest::Model::ClassProxy
+ include CouchRest::Model::Collection
+ include CouchRest::Model::AttributeProtection
+ include CouchRest::Model::Attributes
+ include CouchRest::Model::Associations
+ include CouchRest::Model::Validations
+
+ def self.subclasses
+ @subclasses ||= []
+ end
+
+ def self.inherited(subklass)
+ super
+ subklass.send(:include, CouchRest::Model::Properties)
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
+ def self.inherited(subklass)
+ super
+ subklass.properties = self.properties.dup
+ # This is nasty:
+ subklass._validators = self._validators.dup
+ end
+ EOS
+ subclasses << subklass
+ end
+
+ # Accessors
+ attr_accessor :casted_by
+
+
+ # Instantiate a new ExtendedDocument by preparing all properties
+ # using the provided document hash.
+ #
+ # Options supported:
+ #
+ # * :directly_set_attributes: true when data comes directly from database
+ #
+ def initialize(doc = {}, options = {})
+ prepare_all_attributes(doc, options)
+ super(doc)
+ unless self['_id'] && self['_rev']
+ self['couchrest-type'] = self.class.to_s
+ end
+ after_initialize if respond_to?(:after_initialize)
+ end
+
+
+ # Temp solution to make the view_by methods available
+ def self.method_missing(m, *args, &block)
+ if has_view?(m)
+ query = args.shift || {}
+ return view(m, query, *args, &block)
+ elsif m.to_s =~ /^find_(by_.+)/
+ view_name = $1
+ if has_view?(view_name)
+ return first_from_view(view_name, *args)
+ end
+ end
+ super
+ end
+
+ ### instance methods
+
+ # Gets a reference to the actual document in the DB
+ # Calls up to the next document if there is one,
+ # Otherwise we're at the top and we return self
+ def base_doc
+ return self if base_doc?
+ @casted_by.base_doc
+ end
+
+ # Checks if we're the top document
+ def base_doc?
+ !@casted_by
+ end
+
+ # for compatibility with old-school frameworks
+ alias :new_record? :new?
+ alias :new_document? :new?
+ end
+ end
+end
diff --git a/lib/couchrest/model/callbacks.rb b/lib/couchrest/model/callbacks.rb
new file mode 100644
index 0000000..f1d16a5
--- /dev/null
+++ b/lib/couchrest/model/callbacks.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+module CouchRest #:nodoc:
+ module Model #:nodoc:
+
+ module Callbacks
+ extend ActiveSupport::Concern
+ included do
+ extend ActiveModel::Callbacks
+
+ define_model_callbacks \
+ :create,
+ :destroy,
+ :save,
+ :update,
+ :validate
+
+ end
+
+ def valid?(*) #nodoc
+ _run_validation_callbacks { super }
+ end
+
+ end
+
+ end
+end
diff --git a/lib/couchrest/casted_array.rb b/lib/couchrest/model/casted_array.rb
similarity index 97%
rename from lib/couchrest/casted_array.rb
rename to lib/couchrest/model/casted_array.rb
index 203c05d..a52f152 100644
--- a/lib/couchrest/casted_array.rb
+++ b/lib/couchrest/model/casted_array.rb
@@ -3,7 +3,7 @@
# elements of the array.
#
-module CouchRest
+module CouchRest::Model
class CastedArray < Array
attr_accessor :casted_by
attr_accessor :property
diff --git a/lib/couchrest/casted_model.rb b/lib/couchrest/model/casted_model.rb
similarity index 72%
rename from lib/couchrest/casted_model.rb
rename to lib/couchrest/model/casted_model.rb
index cbe2638..6aa9bce 100644
--- a/lib/couchrest/casted_model.rb
+++ b/lib/couchrest/model/casted_model.rb
@@ -1,13 +1,16 @@
-module CouchRest
+module CouchRest::Model
module CastedModel
-
- def self.included(base)
- base.send(:include, ::CouchRest::Mixins::AttributeProtection)
- base.send(:include, ::CouchRest::Mixins::Attributes)
- base.send(:include, ::CouchRest::Mixins::Callbacks)
- base.send(:include, ::CouchRest::Mixins::Properties)
- base.send(:include, ::CouchRest::Mixins::Associations)
- base.send(:attr_accessor, :casted_by)
+
+ extend ActiveSupport::Concern
+
+ included do
+ include CouchRest::Model::AttributeProtection
+ include CouchRest::Model::Attributes
+ include CouchRest::Model::Callbacks
+ include CouchRest::Model::Properties
+ include CouchRest::Model::Associations
+ include CouchRest::Model::Validations
+ attr_accessor :casted_by
end
def initialize(keys = {})
diff --git a/lib/couchrest/mixins/class_proxy.rb b/lib/couchrest/model/class_proxy.rb
similarity index 89%
rename from lib/couchrest/mixins/class_proxy.rb
rename to lib/couchrest/model/class_proxy.rb
index f6285d8..36200f9 100644
--- a/lib/couchrest/mixins/class_proxy.rb
+++ b/lib/couchrest/model/class_proxy.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module ClassProxy
def self.included(base)
@@ -36,7 +36,7 @@ module CouchRest
@database = database
end
- # ExtendedDocument
+ # Base
def new(*args)
doc = @klass.new(*args)
@@ -57,7 +57,7 @@ module CouchRest
super
end
- # Mixins::DocumentQueries
+ # DocumentQueries
def all(opts = {}, &block)
docs = @klass.all({:database => @database}.merge(opts), &block)
@@ -82,7 +82,7 @@ module CouchRest
end
alias :find :get
- # Mixins::Views
+ # Views
def has_view?(view)
@klass.has_view?(view)
@@ -102,7 +102,7 @@ module CouchRest
doc
end
- # Mixins::DesignDoc
+ # DesignDoc
def design_doc
@klass.design_doc
@@ -116,16 +116,6 @@ module CouchRest
@klass.save_design_doc(@database)
end
- # DEPRICATED
- def all_design_doc_versions
- @klass.all_design_doc_versions(@database)
- end
-
- def stored_design_doc
- @klass.stored_design_doc(@database)
- end
- alias :model_design_doc :stored_design_doc
-
end
end
end
diff --git a/lib/couchrest/mixins/collection.rb b/lib/couchrest/model/collection.rb
similarity index 99%
rename from lib/couchrest/mixins/collection.rb
rename to lib/couchrest/model/collection.rb
index b6bc02b..17910e7 100644
--- a/lib/couchrest/mixins/collection.rb
+++ b/lib/couchrest/model/collection.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module Collection
def self.included(base)
diff --git a/lib/couchrest/mixins/design_doc.rb b/lib/couchrest/model/design_doc.rb
similarity index 98%
rename from lib/couchrest/mixins/design_doc.rb
rename to lib/couchrest/model/design_doc.rb
index 47f3d5a..8cdc8a7 100644
--- a/lib/couchrest/mixins/design_doc.rb
+++ b/lib/couchrest/model/design_doc.rb
@@ -1,7 +1,6 @@
-require 'digest/md5'
-
+# encoding: utf-8
module CouchRest
- module Mixins
+ module Model
module DesignDoc
def self.included(base)
diff --git a/lib/couchrest/mixins/document_queries.rb b/lib/couchrest/model/document_queries.rb
similarity index 99%
rename from lib/couchrest/mixins/document_queries.rb
rename to lib/couchrest/model/document_queries.rb
index e40ccb6..eb3e7da 100644
--- a/lib/couchrest/mixins/document_queries.rb
+++ b/lib/couchrest/model/document_queries.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module DocumentQueries
def self.included(base)
diff --git a/lib/couchrest/model/errors.rb b/lib/couchrest/model/errors.rb
new file mode 100644
index 0000000..7f14fca
--- /dev/null
+++ b/lib/couchrest/model/errors.rb
@@ -0,0 +1,23 @@
+# encoding: utf-8
+module CouchRest
+ module Model
+ module Errors
+
+ class CouchRestModelError < StandardError; end
+
+ # Raised when a persisence method ending in ! fails validation. The message
+ # will contain the full error messages from the +Document+ in question.
+ #
+ # Example:
+ #
+ # Validations.new(person.errors)
+ class Validations < CouchRestModelError
+ attr_reader :document
+ def initialize(document)
+ @document = document
+ super("Validation Failed: #{@document.errors.full_messages.join(", ")}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/couchrest/mixins/extended_attachments.rb b/lib/couchrest/model/extended_attachments.rb
similarity index 99%
rename from lib/couchrest/mixins/extended_attachments.rb
rename to lib/couchrest/model/extended_attachments.rb
index 0f928bd..8d1e6ed 100644
--- a/lib/couchrest/mixins/extended_attachments.rb
+++ b/lib/couchrest/model/extended_attachments.rb
@@ -1,5 +1,5 @@
module CouchRest
- module Mixins
+ module Model
module ExtendedAttachments
# Add a file attachment to the current document. Expects
diff --git a/lib/couchrest/model/persistence.rb b/lib/couchrest/model/persistence.rb
new file mode 100644
index 0000000..dea3e45
--- /dev/null
+++ b/lib/couchrest/model/persistence.rb
@@ -0,0 +1,141 @@
+module CouchRest
+ module Model
+ module Persistence
+ extend ActiveSupport::Concern
+
+ # Create the document. Validation is enabled by default and will return
+ # false if the document is not valid. If all goes well, the document will
+ # be returned.
+ def create(options = {})
+ return false unless perform_validations(options)
+ _run_create_callbacks do
+ _run_save_callbacks do
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
+ result = database.save_doc(self)
+ (result["ok"] == true) ? self : false
+ end
+ end
+ end
+
+ # Creates the document in the db. Raises an exception
+ # if the document is not created properly.
+ def create!
+ self.class.fail_validate!(self) unless self.create
+ end
+
+ # Trigger the callbacks (before, after, around)
+ # only if the document isn't new
+ def update(options = {})
+ raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
+ return false unless perform_validations(options)
+ _run_update_callbacks do
+ _run_save_callbacks do
+ result = database.save_doc(self)
+ result["ok"] == true
+ end
+ end
+ end
+
+ # Trigger the callbacks (before, after, around) and save the document
+ def save(options = {})
+ self.new? ? create(options) : update(options)
+ end
+
+ # Saves the document to the db using save. Raises an exception
+ # if the document is not saved properly.
+ def save!
+ self.class.fail_validate!(self) unless self.save
+ true
+ end
+
+ # Deletes the document from the database. Runs the :destroy callbacks.
+ # Removes the _id and _rev fields, preparing the
+ # document to be saved to a new _id if required.
+ def destroy
+ _run_destroy_callbacks do
+ result = database.delete_doc(self)
+ if result['ok']
+ self.delete('_rev')
+ self.delete('_id')
+ end
+ result['ok']
+ end
+ end
+
+ protected
+
+ def perform_validations(options = {})
+ perform_validation = case options
+ when Hash
+ options[:validate] != false
+ else
+ options
+ end
+ perform_validation ? valid? : true
+ end
+
+
+ module ClassMethods
+
+ # Creates a new instance, bypassing attribute protection
+ #
+ #
+ # ==== Returns
+ # a document instance
+ def create_from_database(doc = {})
+ base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
+ base.new(doc, :directly_set_attributes => true)
+ end
+
+ # Defines an instance and save it directly to the database
+ #
+ # ==== Returns
+ # returns the reloaded document
+ def create(attributes = {})
+ instance = new(attributes)
+ instance.create
+ instance
+ end
+
+ # Defines an instance and save it directly to the database
+ #
+ # ==== Returns
+ # returns the reloaded document or raises an exception
+ def create!(attributes = {})
+ instance = new(attributes)
+ instance.create!
+ instance
+ 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
+
+ # Raise an error if validation failed.
+ def fail_validate!(document)
+ raise Errors::Validations.new(document)
+ end
+ end
+
+
+ end
+ end
+end
diff --git a/lib/couchrest/mixins/properties.rb b/lib/couchrest/model/properties.rb
similarity index 79%
rename from lib/couchrest/mixins/properties.rb
rename to lib/couchrest/model/properties.rb
index 44fd1cf..ebc784d 100644
--- a/lib/couchrest/mixins/properties.rb
+++ b/lib/couchrest/model/properties.rb
@@ -1,9 +1,6 @@
-require 'time'
-require File.join(File.dirname(__FILE__), '..', 'property')
-require File.join(File.dirname(__FILE__), '..', 'casted_array')
-
+# encoding: utf-8
module CouchRest
- module Mixins
+ module Model
module Properties
class IncludeError < StandardError; end
@@ -30,7 +27,7 @@ module CouchRest
end
def write_attribute(property, value)
- prop = property.is_a?(::CouchRest::Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
+ prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
raise "Missing property definition for #{property.to_s}" unless prop
self[prop.to_s] = prop.cast(self, value)
end
@@ -59,6 +56,21 @@ module CouchRest
define_property(name, opts, &block)
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!
+ class_eval <<-EOS, __FILE__, __LINE__
+ property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
+ property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
+
+ set_callback :save, :before do |object|
+ write_attribute('updated_at', Time.now)
+ write_attribute('created_at', Time.now) if object.new?
+ end
+ EOS
+ end
protected
@@ -74,9 +86,12 @@ module CouchRest
type.class_eval { yield type }
type = [type] # inject as an array
end
- property = CouchRest::Property.new(name, type, options)
+ property = Property.new(name, type, options)
create_property_getter(property)
create_property_setter(property) unless property.read_only == true
+ if property.type_class.respond_to?(:validates_casted_model)
+ validates_casted_model property.name
+ end
properties << property
property
end
diff --git a/lib/couchrest/property.rb b/lib/couchrest/model/property.rb
similarity index 74%
rename from lib/couchrest/property.rb
rename to lib/couchrest/model/property.rb
index 1804901..fd2e104 100644
--- a/lib/couchrest/property.rb
+++ b/lib/couchrest/model/property.rb
@@ -1,14 +1,10 @@
-
-require File.join(File.dirname(__FILE__), 'mixins', 'typecast')
-
-module CouchRest
-
- # Basic attribute support for adding getter/setter + validation
+# encoding: utf-8
+module CouchRest::Model
class Property
- include ::CouchRest::Mixins::Typecast
+ include ::CouchRest::Model::Typecast
- attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
+ attr_reader :name, :type, :type_class, :read_only, :alias, :default, :casted, :init_method, :options
# Attribute to define.
# All Properties are assumed casted unless the type is nil.
@@ -42,7 +38,7 @@ module CouchRest
end
arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray
- value = type_class != String ? ::CouchRest::CastedArray.new(arr, self) : arr
+ value = type_class != String ? CastedArray.new(arr, self) : arr
value.casted_by = parent if value.respond_to?(:casted_by)
elsif !value.nil?
value = cast_value(parent, value)
@@ -66,17 +62,6 @@ module CouchRest
end
end
- # Always provide the basic type as a class. If the type
- # is an array, the class will be extracted.
- def type_class
- return String unless casted # This is rubbish, to handle validations
- return @type_class unless @type_class.nil?
- base = @type.is_a?(Array) ? @type.first : @type
- base = String if base.nil?
- base = TrueClass if base.is_a?(String) && base.downcase == 'boolean'
- @type_class = base.is_a?(Class) ? base : base.constantize
- end
-
private
def associate_casted_value_to_parent(parent, value)
@@ -88,7 +73,12 @@ module CouchRest
if type.nil?
@casted = false
@type = nil
+ @type_class = nil
else
+ base = type.is_a?(Array) ? type.first : type
+ base = Object if base.nil?
+ raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
+ @type_class = base
@type = type
end
end
diff --git a/lib/couchrest/support/couchrest.rb b/lib/couchrest/model/support/couchrest.rb
similarity index 50%
rename from lib/couchrest/support/couchrest.rb
rename to lib/couchrest/model/support/couchrest.rb
index e795556..6fde806 100644
--- a/lib/couchrest/support/couchrest.rb
+++ b/lib/couchrest/model/support/couchrest.rb
@@ -5,13 +5,13 @@ module CouchRest
alias :delete_old! :delete!
def delete!
- clear_extended_doc_fresh_cache
+ clear_model_fresh_cache
delete_old!
end
# If the database is deleted, ensure that the design docs will be refreshed.
- def clear_extended_doc_fresh_cache
- ::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
+ def clear_model_fresh_cache
+ ::CouchRest::Model::Base.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
end
end
diff --git a/lib/couchrest/support/rails.rb b/lib/couchrest/model/support/rails.rb
similarity index 65%
rename from lib/couchrest/support/rails.rb
rename to lib/couchrest/model/support/rails.rb
index 997c2c6..f14d2da 100644
--- a/lib/couchrest/support/rails.rb
+++ b/lib/couchrest/model/support/rails.rb
@@ -21,7 +21,7 @@ CouchRest::Document.class_eval do
alias_method :kind_of?, :is_a?
end
-CouchRest::CastedModel.class_eval do
+CouchRest::Model::CastedModel.class_eval do
# The to_param method is needed for rails to generate resourceful routes.
# In your controller, remember that it's actually the id of the document.
def id
@@ -30,13 +30,3 @@ CouchRest::CastedModel.class_eval do
end
alias_method :to_param, :id
end
-
-require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
-
-CouchRest::Validation::ValidationErrors.class_eval do
- # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
- # This method is called by error_messages_for
- def count
- errors.values.inject(0) { |error_count, errors_for_attribute| error_count + errors_for_attribute.size }
- end
-end
diff --git a/lib/couchrest/mixins/typecast.rb b/lib/couchrest/model/typecast.rb
similarity index 98%
rename from lib/couchrest/mixins/typecast.rb
rename to lib/couchrest/model/typecast.rb
index da7c6db..c4aa934 100644
--- a/lib/couchrest/mixins/typecast.rb
+++ b/lib/couchrest/model/typecast.rb
@@ -1,7 +1,3 @@
-require 'time'
-require 'bigdecimal'
-require 'bigdecimal/util'
-
class Time
# returns a local time value much faster than Time.parse
def self.mktime_with_offset(string)
@@ -22,7 +18,7 @@ class Time
end
module CouchRest
- module Mixins
+ module Model
module Typecast
def typecast_value(value, property) # klass, init_method)
diff --git a/lib/couchrest/model/validations.rb b/lib/couchrest/model/validations.rb
new file mode 100644
index 0000000..20c95c2
--- /dev/null
+++ b/lib/couchrest/model/validations.rb
@@ -0,0 +1,32 @@
+# encoding: utf-8
+
+require "couchrest/model/validations/casted_model"
+
+module CouchRest
+ module Model
+
+ # Validations may be applied to both Model::Base and Model::CastedModel
+ module Validations
+ extend ActiveSupport::Concern
+ included do
+ include ActiveModel::Validations
+ end
+
+
+ module ClassMethods
+
+ # Validates the associated casted model. This method should not be
+ # used within your code as it is automatically included when a CastedModel
+ # is used inside the model.
+ #
+ def validates_casted_model(*args)
+ validates_with(CastedModelValidator, _merge_attributes(args))
+ end
+
+ # TODO: Here will lie validates_uniqueness_of
+
+ end
+
+ end
+ end
+end
diff --git a/lib/couchrest/model/validations/casted_model.rb b/lib/couchrest/model/validations/casted_model.rb
new file mode 100644
index 0000000..b4e89e6
--- /dev/null
+++ b/lib/couchrest/model/validations/casted_model.rb
@@ -0,0 +1,14 @@
+module CouchRest
+ module Model
+ module Validations
+ class CastedModelValidator < ActiveModel::EachValidator
+
+ def validate_each(document, attribute, value)
+ values = value.is_a?(Array) ? value : [value]
+ return if values.collect {|doc| doc.nil? || doc.valid? }.all?
+ document.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/model/views.rb
similarity index 98%
rename from lib/couchrest/mixins/views.rb
rename to lib/couchrest/model/views.rb
index f4b55a8..8e2e0d7 100644
--- a/lib/couchrest/mixins/views.rb
+++ b/lib/couchrest/model/views.rb
@@ -1,10 +1,7 @@
module CouchRest
- module Mixins
+ module Model
module Views
-
- def self.included(base)
- base.extend(ClassMethods)
- end
+ extend ActiveSupport::Concern
module ClassMethods
# Define a CouchDB view. The name of the view will be the concatenation
diff --git a/lib/couchrest/validation.rb b/lib/couchrest/validation.rb
deleted file mode 100644
index b0b7604..0000000
--- a/lib/couchrest/validation.rb
+++ /dev/null
@@ -1,244 +0,0 @@
-# Extracted from dm-validations 0.9.10
-#
-# Copyright (c) 2007 Guy van den Berg
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-class Object
- def validatable?
- false
- end
-end
-
-require 'pathname'
-
-dir = File.join(Pathname(__FILE__).dirname.expand_path, 'validation')
-
-require File.join(dir, 'validation_errors')
-require File.join(dir, 'contextual_validators')
-require File.join(dir, 'auto_validate')
-
-require File.join(dir, 'validators', 'generic_validator')
-require File.join(dir, 'validators', 'required_field_validator')
-require File.join(dir, 'validators', 'absent_field_validator')
-require File.join(dir, 'validators', 'format_validator')
-require File.join(dir, 'validators', 'length_validator')
-require File.join(dir, 'validators', 'numeric_validator')
-require File.join(dir, 'validators', 'method_validator')
-require File.join(dir, 'validators', 'confirmation_validator')
-
-module CouchRest
- module Validation
-
- def self.included(base)
- base.extlib_inheritable_accessor(:auto_validation)
- base.class_eval <<-EOS, __FILE__, __LINE__ + 1
- # Callbacks
- define_callbacks :validate
-
- # Turn off auto validation by default
- self.auto_validation ||= false
-
- # Force the auto validation for the class properties
- # This feature is still not fully ported over,
- # test are lacking, so please use with caution
- def self.auto_validate!
- self.auto_validation = true
- end
-
- # share the validations with subclasses
- def self.inherited(subklass)
- self.validators.contexts.each do |k, v|
- subklass.validators.contexts[k] = v.dup
- end
- super
- end
- EOS
-
- base.extend(ClassMethods)
- base.class_eval <<-EOS, __FILE__, __LINE__ + 1
- define_callbacks :validate
- if method_defined?(:_run_save_callbacks)
- set_callback :save, :before, :check_validations
- end
- EOS
- base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def self.define_property(name, options={}, &block)
- property = super
- auto_generate_validations(property) unless property.nil?
- end
- RUBY_EVAL
- end
-
- # Ensures the object is valid for the context provided, and otherwise
- # throws :halt and returns false.
- #
- def check_validations(context = :default)
- throw(:halt, false) unless context.nil? || valid?(context)
- end
-
- # Return the ValidationErrors
- #
- def errors
- @errors ||= ValidationErrors.new
- end
-
- # Mark this resource as validatable. When we validate associations of a
- # resource we can check if they respond to validatable? before trying to
- # recursivly validate them
- #
- def validatable?
- true
- end
-
- # Alias for valid?(:default)
- #
- def valid_for_default?
- valid?(:default)
- end
-
- # Check if a resource is valid in a given context
- #
- def valid?(context = :default)
- recursive_valid?(self, context, true)
- end
-
- # checking on casted objects
- def validate_casted_arrays
- result = true
- array_casted_properties = self.class.properties.select { |property| property.casted && property.type.instance_of?(Array) }
- array_casted_properties.each do |property|
- casted_values = self.send(property.name)
- next unless casted_values.is_a?(Array) && casted_values.first.respond_to?(:valid?)
- casted_values.each do |value|
- result = (result && value.valid?) if value.respond_to?(:valid?)
- end
- end
- result
- end
-
- # Do recursive validity checking
- #
- def recursive_valid?(target, context, state)
- valid = state
- target.each do |key, prop|
- if prop.is_a?(Array)
- prop.each do |item|
- if item.validatable?
- valid = recursive_valid?(item, context, valid) && valid
- end
- end
- elsif prop.validatable?
- valid = recursive_valid?(prop, context, valid) && valid
- end
- end
- target._run_validate_callbacks do
- target.class.validators.execute(context, target) && valid
- end
- end
-
-
- def validation_property_value(name)
- self.respond_to?(name, true) ? self.send(name) : nil
- end
-
- # Get the corresponding Object property, if it exists.
- def validation_property(field_name)
- properties.find{|p| p.name == field_name}
- end
-
- module ClassMethods
- include CouchRest::Validation::ValidatesPresent
- include CouchRest::Validation::ValidatesAbsent
- include CouchRest::Validation::ValidatesIsConfirmed
- # include CouchRest::Validation::ValidatesIsPrimitive
- # include CouchRest::Validation::ValidatesIsAccepted
- include CouchRest::Validation::ValidatesFormat
- include CouchRest::Validation::ValidatesLength
- # include CouchRest::Validation::ValidatesWithin
- include CouchRest::Validation::ValidatesIsNumber
- include CouchRest::Validation::ValidatesWithMethod
- # include CouchRest::Validation::ValidatesWithBlock
- # include CouchRest::Validation::ValidatesIsUnique
- include CouchRest::Validation::AutoValidate
-
- # Return the set of contextual validators or create a new one
- #
- def validators
- @validations ||= ContextualValidators.new
- end
-
- # Clean up the argument list and return a opts hash, including the
- # merging of any default opts. Set the context to default if none is
- # provided. Also allow :context to be aliased to :on, :when & group
- #
- def opts_from_validator_args(args, defaults = nil)
- opts = args.last.kind_of?(Hash) ? args.pop : {}
- context = :default
- context = opts[:context] if opts.has_key?(:context)
- context = opts.delete(:on) if opts.has_key?(:on)
- context = opts.delete(:when) if opts.has_key?(:when)
- context = opts.delete(:group) if opts.has_key?(:group)
- opts[:context] = context
- opts.merge!(defaults) unless defaults.nil?
- opts
- end
-
- # Given a new context create an instance method of
- # valid_for_? which simply calls valid?(context)
- # if it does not already exist
- #
- def create_context_instance_methods(context)
- name = "valid_for_#{context.to_s}?" # valid_for_signup?
- if !self.instance_methods.include?(name)
- class_eval <<-EOS, __FILE__, __LINE__ + 1
- def #{name} # def valid_for_signup?
- valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
- end # end
- EOS
- end
- end
-
- # Create a new validator of the given klazz and push it onto the
- # requested context for each of the attributes in the fields list
- #
- def add_validator_to_context(opts, fields, klazz)
- fields.each do |field|
- validator = klazz.new(field.to_sym, opts)
- if opts[:context].is_a?(Symbol)
- unless validators.context(opts[:context]).include?(validator)
- validators.context(opts[:context]) << validator
- create_context_instance_methods(opts[:context])
- end
- elsif opts[:context].is_a?(Array)
- opts[:context].each do |c|
- unless validators.context(c).include?(validator)
- validators.context(c) << validator
- create_context_instance_methods(c)
- end
- end
- end
- end
- end
-
- end # module ClassMethods
- end # module Validation
-
-end # module CouchRest
diff --git a/lib/couchrest/validation/auto_validate.rb b/lib/couchrest/validation/auto_validate.rb
deleted file mode 100644
index 06e8070..0000000
--- a/lib/couchrest/validation/auto_validate.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-# Ported from dm-migrations
-
-module CouchRest
-
- class Property
- # flag letting us know if we already checked the autovalidation settings
- attr_accessor :autovalidation_check
- @autovalidation_check = false
- end
-
- module Validation
- module AutoValidate
-
- # # Force the auto validation for the class properties
- # # This feature is still not fully ported over,
- # # test are lacking, so please use with caution
- # def auto_validate!
- # auto_validation = true
- # end
-
- # adds message for validator
- def options_with_message(base_options, property, validator_name)
- options = base_options.clone
- opts = property.options
- options[:message] = if opts[:messages]
- if opts[:messages].is_a?(Hash) and msg = opts[:messages][validator_name]
- msg
- else
- nil
- end
- elsif opts[:message]
- opts[:message]
- else
- nil
- end
- options
- end
-
-
- ##
- # Auto-generate validations for a given property. This will only occur
- # if the option :auto_validation is either true or left undefined.
- #
- # @details [Triggers]
- # Triggers that generate validator creation
- #
- # :nullable => false
- # Setting the option :nullable to false causes a
- # validates_presence_of validator to be automatically created on
- # the property
- #
- # :size => 20 or :length => 20
- # Setting the option :size or :length causes a validates_length_of
- # validator to be automatically created on the property. If the
- # value is a Integer the validation will set :maximum => value if
- # the value is a Range the validation will set :within => value
- #
- # :format => :predefined / lambda / Proc
- # Setting the :format option causes a validates_format_of
- # validator to be automatically created on the property
- #
- # :set => ["foo", "bar", "baz"]
- # Setting the :set option causes a validates_within
- # validator to be automatically created on the property
- #
- # Integer type
- # Using a Integer type causes a validates_numericality_of
- # validator to be created for the property. integer_only
- # is set to true
- #
- # Float type
- # Using a Integer type causes a validates_is_number
- # validator to be created for the property. integer_only
- # is set to false, and precision/scale match the property
- #
- #
- # Messages
- #
- # :messages => {..}
- # Setting :messages hash replaces standard error messages
- # with custom ones. For instance:
- # :messages => {:presence => "Field is required",
- # :format => "Field has invalid format"}
- # Hash keys are: :presence, :format, :length, :is_unique,
- # :is_number, :is_primitive
- #
- # :message => "Some message"
- # It is just shortcut if only one validation option is set
- #
- def auto_generate_validations(property)
- return unless ((property.autovalidation_check != true) && self.auto_validation)
- return if (property.options && (property.options.has_key?(:auto_validation) && !property.options[:auto_validation]) || property.read_only)
- # value is set by the storage system
- opts = {}
- opts[:context] = property.options[:validates] if property.options.has_key?(:validates)
-
- # presence
- if opts[:allow_nil] == false
- validates_presence_of property.name, options_with_message(opts, property, :presence)
- end
-
- # length
- if property.type_class == String
- # XXX: maybe length should always return a Range, with the min defaulting to 1
- # 52 being the max set
- len = property.options.fetch(:length, property.options.fetch(:size, 52))
- if len.is_a?(Range)
- opts[:within] = len
- else
- opts[:maximum] = len
- end
- validates_length_of property.name, options_with_message(opts, property, :length)
- end
-
- # format
- if property.options.has_key?(:format)
- opts[:with] = property.options[:format]
- # validates_format property.name, opts
- validates_format property.name, options_with_message(opts, property, :format)
- end
-
- # uniqueness validator
- if property.options.has_key?(:unique)
- value = property.options[:unique]
- if value.is_a?(Array) || value.is_a?(Symbol)
- # validates_is_unique property.name, :scope => Array(value)
- validates_is_unique property.name, options_with_message({:scope => Array(value)}, property, :is_unique)
- elsif value.is_a?(TrueClass)
- # validates_is_unique property.name
- validates_is_unique property.name, options_with_message({}, property, :is_unique)
- end
- end
-
- # within validator
- if property.options.has_key?(:set)
- validates_within property.name, options_with_message({:set => property.options[:set]}, property, :within)
- end
-
- # numeric validator
- if property.type_class == Integer
- opts[:integer_only] = true
- validates_numericality_of property.name, options_with_message(opts, property, :is_number)
- elsif Float == property.type_class
- opts[:precision] = property.precision
- opts[:scale] = property.scale
- validates_numericality_of property.name, options_with_message(opts, property, :is_number)
- end
-
- # marked the property as checked
- property.autovalidation_check = true
-
- end
-
- end # module AutoValidate
- end # module Validation
-end # module CouchRest
diff --git a/lib/couchrest/validation/contextual_validators.rb b/lib/couchrest/validation/contextual_validators.rb
deleted file mode 100644
index 9f2304c..0000000
--- a/lib/couchrest/validation/contextual_validators.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# Extracted from dm-validations 0.9.10
-#
-# Copyright (c) 2007 Guy van den Berg
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-module CouchRest
- module Validation
-
- ##
- #
- # @author Guy van den Berg
- # @since 0.9
- class ContextualValidators
-
- def dump
- contexts.each_pair do |key, context|
- puts "Key=#{key} Context: #{context}"
- end
- end
-
- # Get a hash of named context validators for the resource
- #
- # @return a hash of validators
- def contexts
- @contexts ||= {}
- end
-
- # Return an array of validators for a named context
- #
- # @return An array of validators
- def context(name)
- contexts[name] ||= []
- end
-
- # Clear all named context validators off of the resource
- #
- def clear!
- contexts.clear
- end
-
- # Execute all validators in the named context against the target
- #
- # @param named_context the context we are validating against
- # @param