Renaming to CouchRest Model
Refactored basic directory structure. Moved to ActiveSupport for Validations and Callbacks. Cleaned up older code, and removed support for text property types.
This commit is contained in:
parent
9f1eea8d32
commit
c280b3a29b
79
README.md
79
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).
|
||||
|
|
17
Rakefile
17
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
|
||||
|
|
19
history.txt
19
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
|
||||
|
|
|
@ -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 <tt>updated_at</tt> and <tt>created_at</tt> 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 <tt>_id</tt>.
|
||||
# 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 <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
||||
# document to be saved to a new <tt>_id</tt>.
|
||||
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
|
|
@ -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')
|
|
@ -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 <name>_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 <name>_callback method also updates the _run_<name>_callbacks
|
||||
# method, which is the public API to run the callbacks.
|
||||
#
|
||||
# Also creates a skip_<name>_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
|
10
lib/couchrest/model.rb
Normal file
10
lib/couchrest/model.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
module CouchRest
|
||||
|
||||
module Model
|
||||
|
||||
VERSION = "1.0.0.beta7"
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module AttributeProtection
|
||||
# Attribute protection from mass assignment to CouchRest properties
|
||||
#
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Attributes
|
||||
|
||||
## Support for handling attributes
|
93
lib/couchrest/model/base.rb
Normal file
93
lib/couchrest/model/base.rb
Normal file
|
@ -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
|
27
lib/couchrest/model/callbacks.rb
Normal file
27
lib/couchrest/model/callbacks.rb
Normal file
|
@ -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
|
|
@ -3,7 +3,7 @@
|
|||
# elements of the array.
|
||||
#
|
||||
|
||||
module CouchRest
|
||||
module CouchRest::Model
|
||||
class CastedArray < Array
|
||||
attr_accessor :casted_by
|
||||
attr_accessor :property
|
|
@ -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 = {})
|
|
@ -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
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Collection
|
||||
|
||||
def self.included(base)
|
|
@ -1,7 +1,6 @@
|
|||
require 'digest/md5'
|
||||
|
||||
# encoding: utf-8
|
||||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module DesignDoc
|
||||
|
||||
def self.included(base)
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module DocumentQueries
|
||||
|
||||
def self.included(base)
|
23
lib/couchrest/model/errors.rb
Normal file
23
lib/couchrest/model/errors.rb
Normal file
|
@ -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:
|
||||
#
|
||||
# <tt>Validations.new(person.errors)</tt>
|
||||
class Validations < CouchRestModelError
|
||||
attr_reader :document
|
||||
def initialize(document)
|
||||
@document = document
|
||||
super("Validation Failed: #{@document.errors.full_messages.join(", ")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module ExtendedAttachments
|
||||
|
||||
# Add a file attachment to the current document. Expects
|
141
lib/couchrest/model/persistence.rb
Normal file
141
lib/couchrest/model/persistence.rb
Normal file
|
@ -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 <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
||||
# document to be saved to a new <tt>_id</tt> 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 <tt>_id</tt>.
|
||||
#
|
||||
# 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
|
|
@ -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 <tt>updated_at</tt> and <tt>created_at</tt> 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
32
lib/couchrest/model/validations.rb
Normal file
32
lib/couchrest/model/validations.rb
Normal file
|
@ -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
|
14
lib/couchrest/model/validations/casted_model.rb
Normal file
14
lib/couchrest/model/validations/casted_model.rb
Normal file
|
@ -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
|
|
@ -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
|
|
@ -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_<context>? 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
|
|
@ -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
|
|
@ -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 <Hash> a hash of validators <GenericValidator>
|
||||
def contexts
|
||||
@contexts ||= {}
|
||||
end
|
||||
|
||||
# Return an array of validators for a named context
|
||||
#
|
||||
# @return <Array> 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 <Symbol> named_context the context we are validating against
|
||||
# @param <Object> target the resource that we are validating
|
||||
# @return <Boolean> true if all are valid, otherwise false
|
||||
def execute(named_context, target)
|
||||
raise(ArgumentError, 'invalid context specified') if !named_context || (contexts.length > 0 && !contexts[named_context])
|
||||
target.errors.clear!
|
||||
result = true
|
||||
context(named_context).each do |validator|
|
||||
next unless validator.execute?(target)
|
||||
result = false unless validator.call(target)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end # module ContextualValidators
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,125 +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 ValidationErrors
|
||||
|
||||
include Enumerable
|
||||
|
||||
@@default_error_messages = {
|
||||
:absent => '%s must be absent',
|
||||
:inclusion => '%s must be one of [%s]',
|
||||
:invalid => '%s has an invalid format',
|
||||
:confirmation => '%s does not match the confirmation',
|
||||
:accepted => "%s is not accepted",
|
||||
:nil => '%s must not be nil',
|
||||
:blank => '%s must not be blank',
|
||||
:length_between => '%s must be between %s and %s characters long',
|
||||
:too_long => '%s must be less than %s characters long',
|
||||
:too_short => '%s must be more than %s characters long',
|
||||
:wrong_length => '%s must be %s characters long',
|
||||
:taken => '%s is already taken',
|
||||
:not_a_number => '%s must be a number',
|
||||
:not_an_integer => '%s must be an integer',
|
||||
:greater_than => '%s must be greater than %s',
|
||||
:greater_than_or_equal_to => "%s must be greater than or equal to %s",
|
||||
:equal_to => "%s must be equal to %s",
|
||||
:less_than => '%s must be less than %s',
|
||||
:less_than_or_equal_to => "%s must be less than or equal to %s",
|
||||
:value_between => '%s must be between %s and %s',
|
||||
:primitive => '%s must be of type %s'
|
||||
}
|
||||
|
||||
# Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
|
||||
cattr_writer :default_error_messages
|
||||
|
||||
def self.default_error_message(key, field, *values)
|
||||
field = field.to_s.humanize
|
||||
@@default_error_messages[key] % [field, *values].flatten
|
||||
end
|
||||
|
||||
# Clear existing validation errors.
|
||||
def clear!
|
||||
errors.clear
|
||||
end
|
||||
|
||||
# Add a validation error. Use the field_name :general if the errors does
|
||||
# not apply to a specific field of the Resource.
|
||||
#
|
||||
# @param <Symbol> field_name the name of the field that caused the error
|
||||
# @param <String> message the message to add
|
||||
def add(field_name, message)
|
||||
(errors[field_name.to_sym] ||= []) << message
|
||||
end
|
||||
|
||||
# Collect all errors into a single list.
|
||||
def full_messages
|
||||
errors.inject([]) do |list, pair|
|
||||
list += pair.last
|
||||
end
|
||||
end
|
||||
|
||||
# Return validation errors for a particular field_name.
|
||||
#
|
||||
# @param <Symbol> field_name the name of the field you want an error for
|
||||
def on(field_name)
|
||||
errors_for_field = errors[field_name.to_sym]
|
||||
errors_for_field.blank? ? nil : errors_for_field
|
||||
end
|
||||
|
||||
def each
|
||||
errors.map.each do |k, v|
|
||||
next if v.blank?
|
||||
yield(v)
|
||||
end
|
||||
end
|
||||
|
||||
def empty?
|
||||
entries.empty?
|
||||
end
|
||||
|
||||
# Return size of errors hash
|
||||
#
|
||||
# Allows us to play nicely with Rails' helpers
|
||||
def count
|
||||
errors.size
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &block)
|
||||
errors.send(meth, *args, &block)
|
||||
end
|
||||
|
||||
private
|
||||
def errors
|
||||
@errors ||= {}
|
||||
end
|
||||
|
||||
end # class ValidationErrors
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,74 +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
|
||||
class AbsentFieldValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.send(field_name)
|
||||
return true if (value.nil? || (value.respond_to?(:empty?) && value.empty?))
|
||||
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:absent, field_name)
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
return false
|
||||
end
|
||||
end # class AbsentFieldValidator
|
||||
|
||||
module ValidatesAbsent
|
||||
|
||||
##
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :unwanted_attribute, String
|
||||
# property :another_unwanted, String
|
||||
# property :yet_again, String
|
||||
#
|
||||
# validates_absent :unwanted_attribute
|
||||
# validates_absent :another_unwanted, :yet_again
|
||||
#
|
||||
# # a call to valid? will return false unless
|
||||
# # all three attributes are blank
|
||||
# end
|
||||
#
|
||||
def validates_absent(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::AbsentFieldValidator)
|
||||
end
|
||||
|
||||
end # module ValidatesAbsent
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,107 +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 ConfirmationValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options = {})
|
||||
super
|
||||
@options = options
|
||||
@field_name, @confirm_field_name = field_name, (options[:confirm] || "#{field_name}_confirmation").to_sym
|
||||
@options[:allow_nil] = true unless @options.has_key?(:allow_nil)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
unless valid?(target)
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:confirmation, field_name)
|
||||
add_error(target, error_message, field_name)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def valid?(target)
|
||||
field_value = target.send(field_name)
|
||||
return true if @options[:allow_nil] && field_value.nil?
|
||||
return false if !@options[:allow_nil] && field_value.nil?
|
||||
|
||||
confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
|
||||
field_value == confirm_value
|
||||
end
|
||||
|
||||
end # class ConfirmationValidator
|
||||
|
||||
module ValidatesIsConfirmed
|
||||
|
||||
##
|
||||
# Validates that the given attribute is confirmed by another attribute.
|
||||
# A common use case scenario is when you require a user to confirm their
|
||||
# password, for which you use both password and password_confirmation
|
||||
# attributes.
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :confirm<Symbol> the attribute that you want to validate
|
||||
# against (default is firstattr_confirmation)
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page < Hash
|
||||
# include CouchRest::ExtendedModel
|
||||
# include CouchRest::Validations
|
||||
#
|
||||
# property :password, String
|
||||
# property :email, String
|
||||
# attr_accessor :password_confirmation
|
||||
# attr_accessor :email_repeated
|
||||
#
|
||||
# validates_confirmation_of :password
|
||||
# validates_confirmation_of :email, :confirm => :email_repeated
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # password == password_confirmation
|
||||
# # and
|
||||
# # email == email_repeated
|
||||
#
|
||||
def validates_confirmation_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::ConfirmationValidator)
|
||||
end
|
||||
|
||||
def validates_is_confirmed(*fields)
|
||||
warn "[DEPRECATION] `validates_is_confirmed` is deprecated. Please use `validates_confirmation_of` instead."
|
||||
validates_confirmation_of(*fields)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end # module ValidatesIsConfirmed
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,122 +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.
|
||||
|
||||
require 'pathname'
|
||||
require Pathname(__FILE__).dirname.expand_path + 'formats/email'
|
||||
require Pathname(__FILE__).dirname.expand_path + 'formats/url'
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class FormatValidator < GenericValidator
|
||||
|
||||
FORMATS = {}
|
||||
include CouchRest::Validation::Format::Email
|
||||
include CouchRest::Validation::Format::Url
|
||||
|
||||
def initialize(field_name, options = {}, &b)
|
||||
super(field_name, options)
|
||||
@field_name, @options = field_name, options
|
||||
@options[:allow_nil] = false unless @options.has_key?(:allow_nil)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.validation_property_value(field_name)
|
||||
return true if @options[:allow_nil] && value.nil?
|
||||
|
||||
validation = @options[:as] || @options[:with]
|
||||
|
||||
raise "No such predefined format '#{validation}'" if validation.is_a?(Symbol) && !FORMATS.has_key?(validation)
|
||||
validator = validation.is_a?(Symbol) ? FORMATS[validation][0] : validation
|
||||
|
||||
valid = case validator
|
||||
when Proc then validator.call(value)
|
||||
when Regexp then value =~ validator
|
||||
else
|
||||
raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{field_name} with #{validator.inspect}"
|
||||
end
|
||||
|
||||
return true if valid
|
||||
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:invalid, field_name)
|
||||
|
||||
field = field_name.to_s.humanize
|
||||
error_message = error_message.call(field, value) if error_message.respond_to?(:call)
|
||||
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
#class UnknownValidationFormat < StandardError; end
|
||||
|
||||
end # class FormatValidator
|
||||
|
||||
module ValidatesFormat
|
||||
|
||||
##
|
||||
# Validates that the attribute is in the specified format. You may use the
|
||||
# :as (or :with, it's an alias) option to specify the pre-defined format
|
||||
# that you want to validate against. You may also specify your own format
|
||||
# via a Proc or Regexp passed to the the :as or :with options.
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :as<Format, Proc, Regexp> the pre-defined format, Proc or Regexp to validate against
|
||||
# @option :with<Format, Proc, Regexp> an alias for :as
|
||||
#
|
||||
# @details [Pre-defined Formats]
|
||||
# :email_address (format is specified in DataMapper::Validation::Format::Email)
|
||||
# :url (format is specified in DataMapper::Validation::Format::Url)
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :email, String
|
||||
# property :zip_code, String
|
||||
#
|
||||
# validates_format_of :email, :as => :email_address
|
||||
# validates_format_of :zip_code, :with => /^\d{5}$/
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # email is formatted like an email address
|
||||
# # and
|
||||
# # zip_code is a string of 5 digits
|
||||
#
|
||||
def validates_format_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::FormatValidator)
|
||||
end
|
||||
|
||||
def validates_format(*fields)
|
||||
warn "[DEPRECATION] `validates_format` is deprecated. Please use `validates_format_of` instead."
|
||||
validates_format_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesFormat
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,66 +0,0 @@
|
|||
# encoding: binary
|
||||
|
||||
# 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
|
||||
module Format
|
||||
module Email
|
||||
|
||||
def self.included(base)
|
||||
CouchRest::Validation::FormatValidator::FORMATS.merge!(
|
||||
:email_address => [ EmailAddress, lambda { |field, value| '%s is not a valid email address'.t(value) }]
|
||||
)
|
||||
end
|
||||
|
||||
# RFC2822 (No attribution reference available)
|
||||
EmailAddress = begin
|
||||
alpha = "a-zA-Z"
|
||||
digit = "0-9"
|
||||
atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
|
||||
dot_atom_text = "#{atext}+([.]#{atext}*)*"
|
||||
dot_atom = "#{dot_atom_text}"
|
||||
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
|
||||
text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
|
||||
quoted_pair = "(\\x5c#{text})"
|
||||
qcontent = "(?:#{qtext}|#{quoted_pair})"
|
||||
quoted_string = "[\"]#{qcontent}+[\"]"
|
||||
atom = "#{atext}+"
|
||||
word = "(?:#{atom}|#{quoted_string})"
|
||||
obs_local_part = "#{word}([.]#{word})*"
|
||||
local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
|
||||
no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
|
||||
dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
|
||||
dcontent = "(?:#{dtext}|#{quoted_pair})"
|
||||
domain_literal = "\\[#{dcontent}+\\]"
|
||||
obs_domain = "#{atom}([.]#{atom})*"
|
||||
domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
|
||||
addr_spec = "#{local_part}\@#{domain}"
|
||||
pattern = /^#{addr_spec}$/
|
||||
end
|
||||
|
||||
end # module Email
|
||||
end # module Format
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,43 +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
|
||||
module Format
|
||||
module Url
|
||||
|
||||
def self.included(base)
|
||||
CouchRest::Validation::FormatValidator::FORMATS.merge!(
|
||||
:url => [ Url, lambda { |field, value| '%s is not a valid URL'.t(value) }]
|
||||
)
|
||||
end
|
||||
|
||||
Url = begin
|
||||
# Regex from http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
|
||||
/(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix
|
||||
end
|
||||
|
||||
end # module Url
|
||||
end # module Format
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,120 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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
|
||||
|
||||
# All validators extend this base class. Validators must:
|
||||
#
|
||||
# * Implement the initialize method to capture its parameters, also calling
|
||||
# super to have this parent class capture the optional, general :if and
|
||||
# :unless parameters.
|
||||
# * Implement the call method, returning true or false. The call method
|
||||
# provides the validation logic.
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
class GenericValidator
|
||||
|
||||
attr_accessor :if_clause, :unless_clause
|
||||
attr_reader :field_name
|
||||
|
||||
# Construct a validator. Capture the :if and :unless clauses when present.
|
||||
#
|
||||
# @param field<String, Symbol> The property specified for validation
|
||||
#
|
||||
# @option :if<Symbol, Proc> The name of a method or a Proc to call to
|
||||
# determine if the validation should occur.
|
||||
# @option :unless<Symbol, Proc> The name of a method or a Proc to call to
|
||||
# determine if the validation should not occur
|
||||
# All additional key/value pairs are passed through to the validator
|
||||
# that is sub-classing this GenericValidator
|
||||
#
|
||||
def initialize(field, opts = {})
|
||||
@if_clause = opts.delete(:if)
|
||||
@unless_clause = opts.delete(:unless)
|
||||
end
|
||||
|
||||
# Add an error message to a target resource. If the error corresponds to a
|
||||
# specific field of the resource, add it to that field, otherwise add it
|
||||
# as a :general message.
|
||||
#
|
||||
# @param <Object> target the resource that has the error
|
||||
# @param <String> message the message to add
|
||||
# @param <Symbol> field_name the name of the field that caused the error
|
||||
#
|
||||
# TODO - should the field_name for a general message be :default???
|
||||
#
|
||||
def add_error(target, message, field_name = :general)
|
||||
target.errors.add(field_name, message)
|
||||
end
|
||||
|
||||
# Call the validator. "call" is used so the operation is BoundMethod and
|
||||
# Block compatible. This must be implemented in all concrete classes.
|
||||
#
|
||||
# @param <Object> target the resource that the validator must be called
|
||||
# against
|
||||
# @return <Boolean> true if valid, otherwise false
|
||||
def call(target)
|
||||
raise NotImplementedError, "CouchRest::Validation::GenericValidator::call must be overriden in a subclass"
|
||||
end
|
||||
|
||||
# Determines if this validator should be run against the
|
||||
# target by evaluating the :if and :unless clauses
|
||||
# optionally passed while specifying any validator.
|
||||
#
|
||||
# @param <Object> target the resource that we check against
|
||||
# @return <Boolean> true if should be run, otherwise false
|
||||
def execute?(target)
|
||||
if unless_clause = self.unless_clause
|
||||
if unless_clause.is_a?(Symbol)
|
||||
return false if target.send(unless_clause)
|
||||
elsif unless_clause.respond_to?(:call)
|
||||
return false if unless_clause.call(target)
|
||||
end
|
||||
end
|
||||
|
||||
if if_clause = self.if_clause
|
||||
if if_clause.is_a?(Symbol)
|
||||
return target.send(if_clause)
|
||||
elsif if_clause.respond_to?(:call)
|
||||
return if_clause.call(target)
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
self.field_name == other.field_name &&
|
||||
self.class == other.class &&
|
||||
self.if_clause == other.if_clause &&
|
||||
self.unless_clause == other.unless_clause &&
|
||||
self.instance_variable_get(:@options) == other.instance_variable_get(:@options)
|
||||
end
|
||||
|
||||
end # class GenericValidator
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,139 +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 LengthValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options)
|
||||
super
|
||||
@field_name = field_name
|
||||
@options = options
|
||||
|
||||
@min = options[:minimum] || options[:min]
|
||||
@max = options[:maximum] || options[:max]
|
||||
@equal = options[:is] || options[:equals]
|
||||
@range = options[:within] || options[:in]
|
||||
|
||||
@validation_method ||= :range if @range
|
||||
@validation_method ||= :min if @min && @max.nil?
|
||||
@validation_method ||= :max if @max && @min.nil?
|
||||
@validation_method ||= :equals unless @equal.nil?
|
||||
end
|
||||
|
||||
def call(target)
|
||||
field_value = target.validation_property_value(field_name)
|
||||
return true if @options[:allow_nil] && field_value.nil?
|
||||
|
||||
field_value = '' if field_value.nil?
|
||||
|
||||
# XXX: HACK seems hacky to do this on every validation, probably should
|
||||
# do this elsewhere?
|
||||
field = field_name.to_s.humanize
|
||||
min = @range ? @range.min : @min
|
||||
max = @range ? @range.max : @max
|
||||
equal = @equal
|
||||
|
||||
case @validation_method
|
||||
when :range then
|
||||
unless valid = @range.include?(field_value.size)
|
||||
error_message = ValidationErrors.default_error_message(:length_between, field, min, max)
|
||||
end
|
||||
when :min then
|
||||
unless valid = field_value.size >= min
|
||||
error_message = ValidationErrors.default_error_message(:too_short, field, min)
|
||||
end
|
||||
when :max then
|
||||
unless valid = field_value.size <= max
|
||||
error_message = ValidationErrors.default_error_message(:too_long, field, max)
|
||||
end
|
||||
when :equals then
|
||||
unless valid = field_value.size == equal
|
||||
error_message = ValidationErrors.default_error_message(:wrong_length, field, equal)
|
||||
end
|
||||
end
|
||||
|
||||
error_message = @options[:message] || error_message
|
||||
|
||||
add_error(target, error_message, field_name) unless valid
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
end # class LengthValidator
|
||||
|
||||
module ValidatesLength
|
||||
|
||||
# Validates that the length of the attribute is equal to, less than,
|
||||
# greater than or within a certain range (depending upon the options
|
||||
# you specify).
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :minimum ensures that the attribute's length is greater than
|
||||
# or equal to the supplied value
|
||||
# @option :min alias for :minimum
|
||||
# @option :maximum ensures the attribute's length is less than or equal
|
||||
# to the supplied value
|
||||
# @option :max alias for :maximum
|
||||
# @option :equals ensures the attribute's length is equal to the
|
||||
# supplied value
|
||||
# @option :is alias for :equals
|
||||
# @option :in<Range> given a Range, ensures that the attributes length is
|
||||
# include?'ed in the Range
|
||||
# @option :within<Range> alias for :in
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property high, Integer
|
||||
# property low, Integer
|
||||
# property just_right, Integer
|
||||
#
|
||||
# validates_length_of :high, :min => 100000000000
|
||||
# validates_length_of :low, :equals => 0
|
||||
# validates_length_of :just_right, :within => 1..10
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # high is greater than or equal to 100000000000
|
||||
# # low is equal to 0
|
||||
# # just_right is between 1 and 10 (inclusive of both 1 and 10)
|
||||
#
|
||||
def validates_length_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::LengthValidator)
|
||||
end
|
||||
|
||||
def validates_length(*fields)
|
||||
warn "[DEPRECATION] `validates_length` is deprecated. Please use `validates_length_of` instead."
|
||||
validates_length_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesLength
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,89 +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 MethodValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options.clone
|
||||
@options[:method] = @field_name unless @options.has_key?(:method)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
result, message = target.send(@options[:method])
|
||||
add_error(target, message, field_name) unless result
|
||||
result
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@options[:method] == other.instance_variable_get(:@options)[:method] && super
|
||||
end
|
||||
end # class MethodValidator
|
||||
|
||||
module ValidatesWithMethod
|
||||
|
||||
##
|
||||
# Validate using the given method. The method given needs to return:
|
||||
# [result::<Boolean>, Error Message::<String>]
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :zip_code, String
|
||||
#
|
||||
# validates_with_method :in_the_right_location?
|
||||
#
|
||||
# def in_the_right_location?
|
||||
# if @zip_code == "94301"
|
||||
# return true
|
||||
# else
|
||||
# return [false, "You're in the wrong zip code"]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # A call to valid? will return false and
|
||||
# # populate the object's errors with "You're in the
|
||||
# # wrong zip code" unless zip_code == "94301"
|
||||
#
|
||||
# # You can also specify field:
|
||||
#
|
||||
# validates_with_method :zip_code, :in_the_right_location?
|
||||
#
|
||||
# # it will add returned error message to :zip_code field
|
||||
#
|
||||
def validates_with_method(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::MethodValidator)
|
||||
end
|
||||
|
||||
end # module ValidatesWithMethod
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,109 +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 NumericValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
@options[:integer_only] = false unless @options.has_key?(:integer_only)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.send(field_name)
|
||||
return true if @options[:allow_nil] && value.nil?
|
||||
|
||||
value = (defined?(BigDecimal) && value.kind_of?(BigDecimal)) ? value.to_s('F') : value.to_s
|
||||
|
||||
error_message = @options[:message]
|
||||
precision = @options[:precision]
|
||||
scale = @options[:scale]
|
||||
|
||||
if @options[:integer_only]
|
||||
return true if value =~ /\A[+-]?\d+\z/
|
||||
error_message ||= ValidationErrors.default_error_message(:not_an_integer, field_name)
|
||||
else
|
||||
# FIXME: if precision and scale are not specified, can we assume that it is an integer?
|
||||
# probably not, as floating point numbers don't have hard
|
||||
# defined scale. the scale floats with the length of the
|
||||
# integral and precision. Ie. if precision = 10 and integral
|
||||
# portion of the number is 9834 (4 digits), the max scale will
|
||||
# be 6 (10 - 4). But if the integral length is 1, max scale
|
||||
# will be (10 - 1) = 9, so 1.234567890.
|
||||
if precision && scale
|
||||
#handles both Float when it has scale specified and BigDecimal
|
||||
if precision > scale && scale > 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision - scale}}|\d{0,#{precision - scale}}\.\d{1,#{scale}})\z/
|
||||
elsif precision > scale && scale == 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
|
||||
elsif precision == scale
|
||||
return true if value =~ /\A[+-]?(?:0(?:\.\d{1,#{scale}})?)\z/
|
||||
else
|
||||
raise ArgumentError, "Invalid precision #{precision.inspect} and scale #{scale.inspect} for #{field_name} (value: #{value.inspect} #{value.class})"
|
||||
end
|
||||
elsif precision && scale.nil?
|
||||
# for floats, if scale is not set
|
||||
|
||||
#total number of digits is less or equal precision
|
||||
return true if value.gsub(/[^\d]/, '').length <= precision
|
||||
|
||||
#number of digits before decimal == precision, and the number is x.0. same as scale = 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
|
||||
else
|
||||
return true if value =~ /\A[+-]?(?:\d+|\d*\.\d+)\z/
|
||||
end
|
||||
error_message ||= ValidationErrors.default_error_message(:not_a_number, field_name)
|
||||
end
|
||||
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
# TODO: check the gt, gte, lt, lte, and eq options
|
||||
|
||||
return false
|
||||
end
|
||||
end # class NumericValidator
|
||||
|
||||
module ValidatesIsNumber
|
||||
|
||||
# Validate whether a field is numeric
|
||||
#
|
||||
def validates_numericality_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::NumericValidator)
|
||||
end
|
||||
|
||||
def validates_is_number(*fields)
|
||||
warn "[DEPRECATION] `validates_is_number` is deprecated. Please use `validates_numericality_of` instead."
|
||||
validates_numericality_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesIsNumber
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,114 +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 RequiredFieldValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.validation_property_value(field_name)
|
||||
property = target.validation_property(field_name.to_s)
|
||||
return true if present?(value, property)
|
||||
|
||||
error_message = @options[:message] || default_error(property)
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Boolean property types are considered present if non-nil.
|
||||
# Other property types are considered present if non-blank.
|
||||
# Non-properties are considered present if non-blank.
|
||||
def present?(value, property)
|
||||
boolean_type?(property) ? !value.nil? : !value.blank?
|
||||
end
|
||||
|
||||
def default_error(property)
|
||||
actual = boolean_type?(property) ? :nil : :blank
|
||||
ValidationErrors.default_error_message(actual, field_name)
|
||||
end
|
||||
|
||||
# Is +property+ a boolean property?
|
||||
#
|
||||
# Returns true for Boolean, ParanoidBoolean, TrueClass, etc. properties.
|
||||
# Returns false for other property types.
|
||||
# Returns false for non-properties.
|
||||
def boolean_type?(property)
|
||||
property ? property.type == 'Boolean' : false
|
||||
end
|
||||
|
||||
end # class RequiredFieldValidator
|
||||
|
||||
module ValidatesPresent
|
||||
|
||||
##
|
||||
# Validates that the specified attribute is present.
|
||||
#
|
||||
# For most property types "being present" is the same as being "not
|
||||
# blank" as determined by the attribute's #blank? method. However, in
|
||||
# the case of Boolean, "being present" means not nil; i.e. true or
|
||||
# false.
|
||||
#
|
||||
# @note
|
||||
# dm-core's support lib adds the blank? method to many classes,
|
||||
# @see lib/dm-core/support/blank.rb (dm-core) for more information.
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :required_attribute, String
|
||||
# property :another_required, String
|
||||
# property :yet_again, String
|
||||
#
|
||||
# validates_presence_of :required_attribute
|
||||
# validates_presence_of :another_required, :yet_again
|
||||
#
|
||||
# # a call to valid? will return false unless
|
||||
# # all three attributes are !blank?
|
||||
# end
|
||||
def validates_presence_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::RequiredFieldValidator)
|
||||
end
|
||||
|
||||
def validates_present(*fields)
|
||||
warn "[DEPRECATION] `validates_present` is deprecated. Please use `validates_presence_of` instead."
|
||||
validates_presence_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesPresent
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
# require File.join(File.dirname(__FILE__), "couchrest", "extended_document")
|
||||
|
||||
gem 'couchrest'
|
||||
|
||||
require 'couchrest'
|
||||
|
||||
require 'active_support/core_ext'
|
||||
require 'active_support/json'
|
||||
require 'mime/types'
|
||||
require "enumerator"
|
||||
|
||||
# Monkey patches applied to couchrest
|
||||
require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'couchrest')
|
||||
|
||||
# Base libraries
|
||||
require File.join(File.dirname(__FILE__), 'couchrest', 'extended_document')
|
||||
require File.join(File.dirname(__FILE__), 'couchrest', 'casted_model')
|
||||
|
||||
# Add rails support *after* everything has loaded
|
||||
require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'rails') if defined?(Rails)
|
||||
|
56
lib/couchrest_model.rb
Normal file
56
lib/couchrest_model.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
|
||||
# require File.join(File.dirname(__FILE__), "couchrest", "extended_document")
|
||||
|
||||
gem 'couchrest'
|
||||
|
||||
require 'couchrest'
|
||||
|
||||
require 'active_support/core_ext'
|
||||
require 'active_support/json'
|
||||
require 'active_model'
|
||||
require "active_model/callbacks"
|
||||
require "active_model/conversion"
|
||||
require "active_model/deprecated_error_methods"
|
||||
require "active_model/errors"
|
||||
require "active_model/naming"
|
||||
require "active_model/serialization"
|
||||
require "active_model/translation"
|
||||
require "active_model/validator"
|
||||
require "active_model/validations"
|
||||
require 'mime/types'
|
||||
require "enumerator"
|
||||
require "time"
|
||||
require 'digest/md5'
|
||||
|
||||
require 'bigdecimal' # used in typecast
|
||||
require 'bigdecimal/util' # used in typecast
|
||||
|
||||
require 'couchrest/model'
|
||||
require 'couchrest/model/errors'
|
||||
require "couchrest/model/persistence"
|
||||
require "couchrest/model/typecast"
|
||||
require "couchrest/model/property"
|
||||
require "couchrest/model/casted_array"
|
||||
require "couchrest/model/properties"
|
||||
require "couchrest/model/validations"
|
||||
require "couchrest/model/callbacks"
|
||||
require "couchrest/model/document_queries"
|
||||
require "couchrest/model/views"
|
||||
require "couchrest/model/design_doc"
|
||||
require "couchrest/model/extended_attachments"
|
||||
require "couchrest/model/class_proxy"
|
||||
require "couchrest/model/collection"
|
||||
require "couchrest/model/attribute_protection"
|
||||
require "couchrest/model/attributes"
|
||||
require "couchrest/model/associations"
|
||||
|
||||
# Monkey patches applied to couchrest
|
||||
require "couchrest/model/support/couchrest"
|
||||
|
||||
# Base libraries
|
||||
require "couchrest/model/casted_model"
|
||||
require "couchrest/model/base"
|
||||
|
||||
# Add rails support *after* everything has loaded
|
||||
require "couchrest/model/support/rails" if defined?(Rails)
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
class Client < CouchRest::ExtendedDocument
|
||||
class Client < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
property :name
|
||||
property :tax_code
|
||||
end
|
||||
|
||||
class SaleEntry < CouchRest::ExtendedDocument
|
||||
class SaleEntry < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
property :description
|
||||
property :price
|
||||
end
|
||||
|
||||
class SaleInvoice < CouchRest::ExtendedDocument
|
||||
class SaleInvoice < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
belongs_to :client
|
||||
|
@ -64,7 +64,7 @@ describe "Assocations" do
|
|||
|
||||
it "should raise error if class name does not exist" do
|
||||
lambda {
|
||||
class TestBadAssoc < CouchRest::ExtendedDocument
|
||||
class TestBadAssoc < CouchRest::Model::Base
|
||||
belongs_to :test_bad_item
|
||||
end
|
||||
}.should raise_error
|
||||
|
@ -99,7 +99,7 @@ describe "Assocations" do
|
|||
it "should create an associated property and collection proxy" do
|
||||
@invoice.respond_to?('entry_ids')
|
||||
@invoice.respond_to?('entry_ids=')
|
||||
@invoice.entries.class.should eql(::CouchRest::CollectionProxy)
|
||||
@invoice.entries.class.should eql(::CouchRest::CollectionOfProxy)
|
||||
end
|
||||
|
||||
it "should allow replacement of objects" do
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
describe "ExtendedDocument attachments" do
|
||||
describe "Model attachments" do
|
||||
|
||||
describe "#has_attachment?" do
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
@obj = Basic.new
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
|
@ -35,7 +35,7 @@ describe "ExtendedDocument attachments" do
|
|||
describe "creating an attachment" do
|
||||
before(:each) do
|
||||
@obj = Basic.new
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
||||
@attachment_name = 'my_attachment'
|
||||
|
@ -44,14 +44,14 @@ describe "ExtendedDocument attachments" do
|
|||
|
||||
it "should create an attachment from file with an extension" do
|
||||
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
||||
end
|
||||
|
||||
it "should create an attachment from file without an extension" do
|
||||
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
reloaded_obj = Basic.get(@obj.id)
|
||||
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
||||
end
|
||||
|
@ -89,7 +89,7 @@ describe "ExtendedDocument attachments" do
|
|||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
@file.rewind
|
||||
@content_type = 'media/mp3'
|
||||
end
|
||||
|
@ -129,7 +129,7 @@ describe "ExtendedDocument attachments" do
|
|||
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
||||
@attachment_name = 'my_attachment'
|
||||
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
||||
@obj.save.should == true
|
||||
@obj.save.should be_true
|
||||
end
|
||||
|
||||
it 'should return nil if attachment does not exist' do
|
|
@ -1,150 +1,153 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
|
||||
describe "ExtendedDocument", "no declarations" do
|
||||
class NoProtection < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :phone
|
||||
end
|
||||
describe "Model Attributes" do
|
||||
|
||||
it "should not protect anything through new" do
|
||||
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
||||
describe "no declarations" do
|
||||
class NoProtection < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :phone
|
||||
end
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
it "should not protect anything through new" do
|
||||
user = NoProtection.new(:name => "will", :phone => "555-5555")
|
||||
|
||||
it "should not protect anything through attributes=" do
|
||||
user = NoProtection.new
|
||||
user.attributes = {:name => "will", :phone => "555-5555"}
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
it "should recreate from the database properly" do
|
||||
user = NoProtection.new
|
||||
user.name = "will"
|
||||
user.phone = "555-5555"
|
||||
user.save!
|
||||
it "should not protect anything through attributes=" do
|
||||
user = NoProtection.new
|
||||
user.attributes = {:name => "will", :phone => "555-5555"}
|
||||
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
|
||||
user = NoProtection.get(user.id)
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "accessible flag" do
|
||||
class WithAccessible < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false
|
||||
end
|
||||
|
||||
it "should recognize accessible properties" do
|
||||
props = WithAccessible.accessible_properties.map { |prop| prop.name}
|
||||
props.should include("name")
|
||||
props.should_not include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithAccessible.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithAccessible.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "protected flag" do
|
||||
class WithProtected < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should recognize protected properties" do
|
||||
props = WithProtected.protected_properties.map { |prop| prop.name}
|
||||
props.should_not include("name")
|
||||
props.should include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithProtected.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithProtected.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "protected flag" do
|
||||
class WithBoth < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should raise an error when both are set" do
|
||||
lambda { WithBoth.new }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "ExtendedDocument", "from database" do
|
||||
class WithProtected < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
view_by :name
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@user = WithProtected.new
|
||||
@user.name = "will"
|
||||
@user.admin = true
|
||||
@user.save!
|
||||
end
|
||||
|
||||
def verify_attrs(user)
|
||||
user.name.should == "will"
|
||||
user.admin.should == true
|
||||
end
|
||||
|
||||
it "ExtendedDocument#get should not strip protected attributes" do
|
||||
reloaded = WithProtected.get( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "ExtendedDocument#get! should not strip protected attributes" do
|
||||
reloaded = WithProtected.get!( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "ExtendedDocument#all should not strip protected attributes" do
|
||||
# all creates a CollectionProxy
|
||||
docs = WithProtected.all(:key => @user.id)
|
||||
docs.size.should == 1
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "views should not strip protected attributes" do
|
||||
docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
it "should recreate from the database properly" do
|
||||
user = NoProtection.new
|
||||
user.name = "will"
|
||||
user.phone = "555-5555"
|
||||
user.save!
|
||||
|
||||
user = NoProtection.get(user.id)
|
||||
user.name.should == "will"
|
||||
user.phone.should == "555-5555"
|
||||
end
|
||||
end
|
||||
|
||||
describe "Model Base", "accessible flag" do
|
||||
class WithAccessible < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false
|
||||
end
|
||||
|
||||
it "should recognize accessible properties" do
|
||||
props = WithAccessible.accessible_properties.map { |prop| prop.name}
|
||||
props.should include("name")
|
||||
props.should_not include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithAccessible.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithAccessible.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "Model Base", "protected flag" do
|
||||
class WithProtected < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should recognize protected properties" do
|
||||
props = WithProtected.protected_properties.map { |prop| prop.name}
|
||||
props.should_not include("name")
|
||||
props.should include("admin")
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through new" do
|
||||
user = WithProtected.new(:name => "will", :admin => true)
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
|
||||
it "should protect non-accessible properties set through attributes=" do
|
||||
user = WithProtected.new
|
||||
user.attributes = {:name => "will", :admin => true}
|
||||
|
||||
user.name.should == "will"
|
||||
user.admin.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "protected flag" do
|
||||
class WithBoth < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, :accessible => true
|
||||
property :admin, :default => false, :protected => true
|
||||
end
|
||||
|
||||
it "should raise an error when both are set" do
|
||||
lambda { WithBoth.new }.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "from database" do
|
||||
class WithProtected < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :admin, :default => false, :protected => true
|
||||
view_by :name
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@user = WithProtected.new
|
||||
@user.name = "will"
|
||||
@user.admin = true
|
||||
@user.save!
|
||||
end
|
||||
|
||||
def verify_attrs(user)
|
||||
user.name.should == "will"
|
||||
user.admin.should == true
|
||||
end
|
||||
|
||||
it "Base#get should not strip protected attributes" do
|
||||
reloaded = WithProtected.get( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "Base#get! should not strip protected attributes" do
|
||||
reloaded = WithProtected.get!( @user.id )
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "Base#all should not strip protected attributes" do
|
||||
# all creates a CollectionProxy
|
||||
docs = WithProtected.all(:key => @user.id)
|
||||
docs.size.should == 1
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
|
||||
it "views should not strip protected attributes" do
|
||||
docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
|
||||
reloaded = docs.first
|
||||
verify_attrs reloaded
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
403
spec/couchrest/base_spec.rb
Normal file
403
spec/couchrest/base_spec.rb
Normal file
|
@ -0,0 +1,403 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'base')
|
||||
|
||||
describe "Model Base" do
|
||||
|
||||
before(:each) do
|
||||
@obj = WithDefaultValues.new
|
||||
end
|
||||
|
||||
describe "instance database connection" do
|
||||
it "should use the default database" do
|
||||
@obj.database.name.should == 'couchrest-model-test'
|
||||
end
|
||||
|
||||
it "should override the default db" do
|
||||
@obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
|
||||
@obj.database.name.should == 'couchrest-extendedmodel-test'
|
||||
@obj.database.delete!
|
||||
end
|
||||
end
|
||||
|
||||
describe "a new model" do
|
||||
it "should be a new document" do
|
||||
@obj = Basic.new
|
||||
@obj.rev.should be_nil
|
||||
@obj.should be_new
|
||||
@obj.should be_new_document
|
||||
@obj.should be_new_record
|
||||
end
|
||||
|
||||
it "should not failed on a nil value in argument" do
|
||||
@obj = Basic.new(nil)
|
||||
@obj.should == { 'couchrest-type' => 'Basic' }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "update attributes without saving" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should work for attribute= methods" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
||||
@art['title'].should == "super danger"
|
||||
end
|
||||
it "should silently ignore _id" do
|
||||
@art.update_attributes_without_saving('_id' => 'foobar')
|
||||
@art['_id'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore _rev" do
|
||||
@art.update_attributes_without_saving('_rev' => 'foobar')
|
||||
@art['_rev'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore created_at" do
|
||||
@art.update_attributes_without_saving('created_at' => 'foobar')
|
||||
@art['created_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore updated_at" do
|
||||
@art.update_attributes_without_saving('updated_at' => 'foobar')
|
||||
@art['updated_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should also work using attributes= alias" do
|
||||
@art.respond_to?(:attributes=).should be_true
|
||||
@art.attributes = {'date' => Time.now, :title => "something else"}
|
||||
@art['title'].should == "something else"
|
||||
end
|
||||
|
||||
it "should not flip out if an attribute= method is missing and ignore it" do
|
||||
lambda {
|
||||
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
}.should_not raise_error
|
||||
@art.slug.should == "big-bad-danger"
|
||||
end
|
||||
|
||||
#it "should not change other attributes if there is an error" do
|
||||
# lambda {
|
||||
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
# }.should raise_error
|
||||
# @art['title'].should == "big bad danger"
|
||||
#end
|
||||
end
|
||||
|
||||
describe "update attributes" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should save" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes('date' => Time.now, :title => "super danger")
|
||||
loaded = Article.get(@art.id)
|
||||
loaded['title'].should == "super danger"
|
||||
end
|
||||
end
|
||||
|
||||
describe "with default" do
|
||||
it "should have the default value set at initalization" do
|
||||
@obj.preset.should == {:right => 10, :top_align => false}
|
||||
end
|
||||
|
||||
it "should have the default false value explicitly assigned" do
|
||||
@obj.default_false.should == false
|
||||
end
|
||||
|
||||
it "should automatically call a proc default at initialization" do
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.set_by_proc.should == @obj.set_by_proc
|
||||
@obj.set_by_proc.should < Time.now
|
||||
end
|
||||
|
||||
it "should let you overwrite the default values" do
|
||||
obj = WithDefaultValues.new(:preset => 'test')
|
||||
obj.preset = 'test'
|
||||
end
|
||||
|
||||
it "should work with a default empty array" do
|
||||
obj = WithDefaultValues.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
|
||||
it "should set default value of read-only property" do
|
||||
obj = WithDefaultValues.new
|
||||
obj.read_only_with_default.should == 'generic'
|
||||
end
|
||||
end
|
||||
|
||||
describe "simplified way of setting property types" do
|
||||
it "should set defaults" do
|
||||
obj = WithSimplePropertyType.new
|
||||
obj.preset.should eql('none')
|
||||
end
|
||||
|
||||
it "should handle arrays" do
|
||||
obj = WithSimplePropertyType.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
end
|
||||
|
||||
describe "a doc with template values (CR::Model spec)" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
@tmpl = WithTemplateAndUniqueID.new
|
||||
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
|
||||
end
|
||||
it "should have fields set when new" do
|
||||
@tmpl.preset.should == 'value'
|
||||
end
|
||||
it "shouldn't override explicitly set values" do
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
end
|
||||
it "shouldn't override existing documents" do
|
||||
@tmpl2.save
|
||||
tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
tmpl2_reloaded.preset.should == 'not_value'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "finding all instances of a model" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '4').save
|
||||
end
|
||||
it "should make the design doc" do
|
||||
WithTemplateAndUniqueID.all
|
||||
d = WithTemplateAndUniqueID.design_doc
|
||||
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
||||
end
|
||||
it "should find all" do
|
||||
rs = WithTemplateAndUniqueID.all
|
||||
rs.length.should == 4
|
||||
end
|
||||
end
|
||||
|
||||
describe "counting all instances of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
end
|
||||
|
||||
it ".count should return 0 if there are no docuemtns" do
|
||||
WithTemplateAndUniqueID.count.should == 0
|
||||
end
|
||||
|
||||
it ".count should return the number of documents" do
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
|
||||
WithTemplateAndUniqueID.count.should == 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding the first instance of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
# WithTemplateAndUniqueID.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '4').save
|
||||
end
|
||||
it "should make the design doc" do
|
||||
WithTemplateAndUniqueID.all
|
||||
d = WithTemplateAndUniqueID.design_doc
|
||||
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
||||
end
|
||||
it "should find first" do
|
||||
rs = WithTemplateAndUniqueID.first
|
||||
rs['important-field'].should == "1"
|
||||
end
|
||||
it "should return nil if no instances are found" do
|
||||
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
||||
WithTemplateAndUniqueID.first.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "lazily refreshing the design document" do
|
||||
before(:all) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
end
|
||||
it "should not save the design doc twice" do
|
||||
WithTemplateAndUniqueID.all
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.refresh_design_doc
|
||||
rev = WithTemplateAndUniqueID.design_doc['_rev']
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.refresh_design_doc
|
||||
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model with a subobject field" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 410",
|
||||
"professor" => {
|
||||
"name" => ["Mark", "Hinchliff"]
|
||||
},
|
||||
"ends_at" => "2008/12/19 13:00:00 +0800"
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course["professor"]["name"][1].should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the professor as a person" do
|
||||
@course['professor'].last_name.should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the ends_at as a Time" do
|
||||
@course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
||||
end
|
||||
end
|
||||
|
||||
describe "timestamping" do
|
||||
before(:each) do
|
||||
oldart = Article.get "saving-this" rescue nil
|
||||
oldart.destroy if oldart
|
||||
@art = Article.new(:title => "Saving this")
|
||||
@art.save
|
||||
end
|
||||
|
||||
it "should define the updated_at and created_at getters and set the values" do
|
||||
@obj.save
|
||||
obj = WithDefaultValues.get(@obj.id)
|
||||
obj.should be_an_instance_of(WithDefaultValues)
|
||||
obj.created_at.should be_an_instance_of(Time)
|
||||
obj.updated_at.should be_an_instance_of(Time)
|
||||
obj.created_at.to_s.should == @obj.updated_at.to_s
|
||||
end
|
||||
|
||||
it "should not change created_at on update" do
|
||||
2.times do
|
||||
lambda do
|
||||
@art.save
|
||||
end.should_not change(@art, :created_at)
|
||||
end
|
||||
end
|
||||
|
||||
it "should set the time on create" do
|
||||
(Time.now - @art.created_at).should < 2
|
||||
foundart = Article.get @art.id
|
||||
foundart.created_at.should == foundart.updated_at
|
||||
end
|
||||
it "should set the time on update" do
|
||||
@art.save
|
||||
@art.created_at.should < @art.updated_at
|
||||
end
|
||||
end
|
||||
|
||||
describe "getter and setter methods" do
|
||||
it "should try to call the arg= method before setting :arg in the hash" do
|
||||
@doc = WithGetterAndSetterMethods.new(:arg => "foo")
|
||||
@doc['arg'].should be_nil
|
||||
@doc[:arg].should be_nil
|
||||
@doc.other_arg.should == "foo-foo"
|
||||
end
|
||||
end
|
||||
|
||||
describe "initialization" do
|
||||
it "should call after_initialize method if available" do
|
||||
@doc = WithAfterInitializeMethod.new
|
||||
@doc['some_value'].should eql('value')
|
||||
end
|
||||
end
|
||||
|
||||
describe "recursive validation on a model" do
|
||||
before :each do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Sockington')
|
||||
end
|
||||
|
||||
it "should not save if a nested casted model is invalid" do
|
||||
@cat.favorite_toy = CatToy.new
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested casted model is valid" do
|
||||
@cat.favorite_toy = CatToy.new(:name => 'Squeaky')
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not save when nested collection contains an invalid casted model" do
|
||||
@cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested collection contains valid casted models" do
|
||||
@cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not fail if the nested casted model doesn't have validation" do
|
||||
Cat.property :trainer, Person
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'Mr Bigglesworth')
|
||||
cat.trainer = Person.new
|
||||
cat.should be_valid
|
||||
cat.save.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "searching the contents of an extended document" do
|
||||
before :each do
|
||||
@db = reset_test_db!
|
||||
|
||||
names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
|
||||
names.each { |name| Cat.create(:name => name) }
|
||||
|
||||
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
|
||||
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
|
||||
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
|
||||
end
|
||||
|
||||
it "should be able to paginate through a large set of search results" do
|
||||
if couchdb_lucene_available?
|
||||
names = []
|
||||
Cat.paginated_each(:design_doc => "search", :view_name => "cats",
|
||||
:q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
|
||||
cat.should_not be_nil
|
||||
names << cat.name
|
||||
end
|
||||
|
||||
names.size.should == 5
|
||||
names.should include('Sockington')
|
||||
names.should include('Smitty')
|
||||
names.should include('Sammy')
|
||||
names.should include('Samson')
|
||||
names.should include('Simon')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,39 +1,33 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'question')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
||||
|
||||
class WithCastedModelMixin < Hash
|
||||
include CouchRest::CastedModel
|
||||
include CouchRest::Model::CastedModel
|
||||
property :name
|
||||
property :no_value
|
||||
property :details, :type => 'Object', :default => {}
|
||||
property :casted_attribute, :cast_as => 'WithCastedModelMixin'
|
||||
property :details, Object, :default => {}
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
end
|
||||
|
||||
class DummyModel < CouchRest::ExtendedDocument
|
||||
class DummyModel < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||
property :casted_attribute, WithCastedModelMixin
|
||||
property :keywords, ["String"]
|
||||
property :keywords, [String]
|
||||
property :sub_models do |child|
|
||||
child.property :title
|
||||
end
|
||||
end
|
||||
|
||||
class CastedCallbackDoc < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||
property :callback_model, :cast_as => 'WithCastedCallBackModel'
|
||||
end
|
||||
class WithCastedCallBackModel < Hash
|
||||
include CouchRest::CastedModel
|
||||
include CouchRest::Validation
|
||||
include CouchRest::Model::CastedModel
|
||||
property :name
|
||||
property :run_before_validate
|
||||
property :run_after_validate
|
||||
|
@ -46,7 +40,13 @@ class WithCastedCallBackModel < Hash
|
|||
end
|
||||
end
|
||||
|
||||
describe CouchRest::CastedModel do
|
||||
class CastedCallbackDoc < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
raise "Default DB not set" if TEST_SERVER.default_database.nil?
|
||||
property :callback_model, WithCastedCallBackModel
|
||||
end
|
||||
|
||||
describe CouchRest::Model::CastedModel do
|
||||
|
||||
describe "A non hash class including CastedModel" do
|
||||
it "should fail raising and include error" do
|
||||
|
@ -227,7 +227,7 @@ describe CouchRest::CastedModel do
|
|||
@cat.toys.push(toy)
|
||||
@cat.save.should be_true
|
||||
@cat = Cat.get @cat.id
|
||||
@cat.toys.class.should == CouchRest::CastedArray
|
||||
@cat.toys.class.should == CouchRest::Model::CastedArray
|
||||
@cat.toys.first.class.should == CatToy
|
||||
@cat.toys.first.should === toy
|
||||
end
|
||||
|
@ -240,7 +240,7 @@ describe CouchRest::CastedModel do
|
|||
end
|
||||
|
||||
it "should not fail if the casted model doesn't have validation" do
|
||||
Cat.property :masters, :cast_as => ['Person'], :default => []
|
||||
Cat.property :masters, [Person], :default => []
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'kitty')
|
||||
cat.should be_valid
|
||||
|
|
|
@ -3,7 +3,7 @@ require File.join(FIXTURE_PATH, 'more', 'cat')
|
|||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
|
||||
class Driver < CouchRest::ExtendedDocument
|
||||
class Driver < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
# You have to add a casted_by accessor if you want to reach a casted extended doc parent
|
||||
attr_accessor :casted_by
|
||||
|
@ -11,12 +11,11 @@ class Driver < CouchRest::ExtendedDocument
|
|||
property :name
|
||||
end
|
||||
|
||||
class Car < CouchRest::ExtendedDocument
|
||||
class Car < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :name
|
||||
property :driver, :cast_as => 'Driver'
|
||||
property :backseat_driver, :cast_as => Driver
|
||||
property :driver, Driver
|
||||
end
|
||||
|
||||
describe "casting an extended document" do
|
||||
|
@ -24,17 +23,14 @@ describe "casting an extended document" do
|
|||
before(:each) do
|
||||
@driver = Driver.new(:name => 'Matt')
|
||||
@car = Car.new(:name => 'Renault 306', :driver => @driver)
|
||||
@car2 = Car.new(:name => 'Renault 306', :backseat_driver => @driver.dup)
|
||||
end
|
||||
|
||||
it "should retain all properties of the casted attribute" do
|
||||
@car.driver.should == @driver
|
||||
@car2.backseat_driver.should == @driver
|
||||
end
|
||||
|
||||
it "should let the casted document know who casted it" do
|
||||
@car.driver.casted_by.should == @car
|
||||
@car2.backseat_driver.casted_by.should == @car2
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,7 +57,7 @@ describe "assigning a value to casted attribute after initializing an object" do
|
|||
|
||||
end
|
||||
|
||||
describe "casting an extended document from parsed JSON" do
|
||||
describe "casting a model from parsed JSON" do
|
||||
|
||||
before(:each) do
|
||||
@driver = Driver.new(:name => 'Matt')
|
|
@ -1,6 +1,6 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
|
||||
class UnattachedDoc < CouchRest::ExtendedDocument
|
||||
class UnattachedDoc < CouchRest::Model::Base
|
||||
# Note: no use_database here
|
||||
property :title
|
||||
property :questions
|
||||
|
|
|
@ -1,869 +0,0 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
|
||||
describe "ExtendedDocument" do
|
||||
|
||||
class WithDefaultValues < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :preset, :type => 'Object', :default => {:right => 10, :top_align => false}
|
||||
property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
|
||||
property :tags, :type => ['String'], :default => []
|
||||
property :read_only_with_default, :default => 'generic', :read_only => true
|
||||
property :default_false, :type => 'Boolean', :default => false
|
||||
property :name
|
||||
timestamps!
|
||||
end
|
||||
|
||||
class WithSimplePropertyType < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, String
|
||||
property :preset, String, :default => 'none'
|
||||
property :tags, [String]
|
||||
timestamps!
|
||||
end
|
||||
|
||||
class WithCallBacks < CouchRest::ExtendedDocument
|
||||
include ::CouchRest::Validation
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :run_before_validate
|
||||
property :run_after_validate
|
||||
property :run_before_save
|
||||
property :run_after_save
|
||||
property :run_before_create
|
||||
property :run_after_create
|
||||
property :run_before_update
|
||||
property :run_after_update
|
||||
|
||||
before_validate do |object|
|
||||
object.run_before_validate = true
|
||||
end
|
||||
after_validate do |object|
|
||||
object.run_after_validate = true
|
||||
end
|
||||
before_save do |object|
|
||||
object.run_before_save = true
|
||||
end
|
||||
after_save do |object|
|
||||
object.run_after_save = true
|
||||
end
|
||||
before_create do |object|
|
||||
object.run_before_create = true
|
||||
end
|
||||
after_create do |object|
|
||||
object.run_after_create = true
|
||||
end
|
||||
before_update do |object|
|
||||
object.run_before_update = true
|
||||
end
|
||||
after_update do |object|
|
||||
object.run_after_update = true
|
||||
end
|
||||
|
||||
property :run_one
|
||||
property :run_two
|
||||
property :run_three
|
||||
|
||||
before_save :run_one_method, :run_two_method do |object|
|
||||
object.run_three = true
|
||||
end
|
||||
def run_one_method
|
||||
self.run_one = true
|
||||
end
|
||||
def run_two_method
|
||||
self.run_two = true
|
||||
end
|
||||
|
||||
attr_accessor :run_it
|
||||
property :conditional_one
|
||||
property :conditional_two
|
||||
|
||||
before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
|
||||
def conditional_one_method
|
||||
self.conditional_one = true
|
||||
end
|
||||
def conditional_two_method
|
||||
self.conditional_two = true
|
||||
end
|
||||
end
|
||||
|
||||
class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
unique_id do |model|
|
||||
model['important-field']
|
||||
end
|
||||
property :preset, :default => 'value'
|
||||
property :has_no_default
|
||||
end
|
||||
|
||||
class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :other_arg
|
||||
def arg
|
||||
other_arg
|
||||
end
|
||||
|
||||
def arg=(value)
|
||||
self.other_arg = "foo-#{value}"
|
||||
end
|
||||
end
|
||||
|
||||
class WithAfterInitializeMethod < CouchRest::ExtendedDocument
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :some_value
|
||||
|
||||
def after_initialize
|
||||
self.some_value ||= "value"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
before(:each) do
|
||||
@obj = WithDefaultValues.new
|
||||
end
|
||||
|
||||
describe "instance database connection" do
|
||||
it "should use the default database" do
|
||||
@obj.database.name.should == 'couchrest-test'
|
||||
end
|
||||
|
||||
it "should override the default db" do
|
||||
@obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
|
||||
@obj.database.name.should == 'couchrest-extendedmodel-test'
|
||||
@obj.database.delete!
|
||||
end
|
||||
end
|
||||
|
||||
describe "a new model" do
|
||||
it "should be a new document" do
|
||||
@obj = Basic.new
|
||||
@obj.rev.should be_nil
|
||||
@obj.should be_new
|
||||
@obj.should be_new_document
|
||||
@obj.should be_new_record
|
||||
end
|
||||
|
||||
it "should not failed on a nil value in argument" do
|
||||
@obj = Basic.new(nil)
|
||||
@obj.should == { 'couchrest-type' => 'Basic' }
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a new document" do
|
||||
it "should instantialize and save a document" do
|
||||
article = Article.create(:title => 'my test')
|
||||
article.title.should == 'my test'
|
||||
article.should_not be_new
|
||||
end
|
||||
|
||||
it "should trigger the create callbacks" do
|
||||
doc = WithCallBacks.create(:name => 'my other test')
|
||||
doc.run_before_create.should be_true
|
||||
doc.run_after_create.should be_true
|
||||
doc.run_before_save.should be_true
|
||||
doc.run_after_save.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a new document from database" do
|
||||
|
||||
it "should instantialize" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize of same class if no couchrest-type included from DB" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize document of different type" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => 'WithCallBacks', 'name' => 'my test'})
|
||||
doc.class.should eql(WithCallBacks)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "update attributes without saving" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should work for attribute= methods" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
|
||||
@art['title'].should == "super danger"
|
||||
end
|
||||
it "should silently ignore _id" do
|
||||
@art.update_attributes_without_saving('_id' => 'foobar')
|
||||
@art['_id'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore _rev" do
|
||||
@art.update_attributes_without_saving('_rev' => 'foobar')
|
||||
@art['_rev'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore created_at" do
|
||||
@art.update_attributes_without_saving('created_at' => 'foobar')
|
||||
@art['created_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should silently ignore updated_at" do
|
||||
@art.update_attributes_without_saving('updated_at' => 'foobar')
|
||||
@art['updated_at'].should_not == 'foobar'
|
||||
end
|
||||
it "should also work using attributes= alias" do
|
||||
@art.respond_to?(:attributes=).should be_true
|
||||
@art.attributes = {'date' => Time.now, :title => "something else"}
|
||||
@art['title'].should == "something else"
|
||||
end
|
||||
|
||||
it "should not flip out if an attribute= method is missing and ignore it" do
|
||||
lambda {
|
||||
@art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
}.should_not raise_error
|
||||
@art.slug.should == "big-bad-danger"
|
||||
end
|
||||
|
||||
#it "should not change other attributes if there is an error" do
|
||||
# lambda {
|
||||
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
# }.should raise_error
|
||||
# @art['title'].should == "big bad danger"
|
||||
#end
|
||||
end
|
||||
|
||||
describe "update attributes" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
a.destroy if a
|
||||
@art = Article.new(:title => "big bad danger")
|
||||
@art.save
|
||||
end
|
||||
it "should save" do
|
||||
@art['title'].should == "big bad danger"
|
||||
@art.update_attributes('date' => Time.now, :title => "super danger")
|
||||
loaded = Article.get(@art.id)
|
||||
loaded['title'].should == "super danger"
|
||||
end
|
||||
end
|
||||
|
||||
describe "with default" do
|
||||
it "should have the default value set at initalization" do
|
||||
@obj.preset.should == {:right => 10, :top_align => false}
|
||||
end
|
||||
|
||||
it "should have the default false value explicitly assigned" do
|
||||
@obj.default_false.should == false
|
||||
end
|
||||
|
||||
it "should automatically call a proc default at initialization" do
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.set_by_proc.should == @obj.set_by_proc
|
||||
@obj.set_by_proc.should < Time.now
|
||||
end
|
||||
|
||||
it "should let you overwrite the default values" do
|
||||
obj = WithDefaultValues.new(:preset => 'test')
|
||||
obj.preset = 'test'
|
||||
end
|
||||
|
||||
it "should work with a default empty array" do
|
||||
obj = WithDefaultValues.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
|
||||
it "should set default value of read-only property" do
|
||||
obj = WithDefaultValues.new
|
||||
obj.read_only_with_default.should == 'generic'
|
||||
end
|
||||
end
|
||||
|
||||
describe "simplified way of setting property types" do
|
||||
it "should set defaults" do
|
||||
obj = WithSimplePropertyType.new
|
||||
obj.preset.should eql('none')
|
||||
end
|
||||
|
||||
it "should handle arrays" do
|
||||
obj = WithSimplePropertyType.new(:tags => ['spec'])
|
||||
obj.tags.should == ['spec']
|
||||
end
|
||||
end
|
||||
|
||||
describe "a doc with template values (CR::Model spec)" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
@tmpl = WithTemplateAndUniqueID.new
|
||||
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
|
||||
end
|
||||
it "should have fields set when new" do
|
||||
@tmpl.preset.should == 'value'
|
||||
end
|
||||
it "shouldn't override explicitly set values" do
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
end
|
||||
it "shouldn't override existing documents" do
|
||||
@tmpl2.save
|
||||
tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
|
||||
@tmpl2.preset.should == 'not_value'
|
||||
tmpl2_reloaded.preset.should == 'not_value'
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model" do
|
||||
before(:all) do
|
||||
@art = Article.new(:title => 'All About Getting')
|
||||
@art.save
|
||||
end
|
||||
it "should load and instantiate it" do
|
||||
foundart = Article.get @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should load and instantiate with find" do
|
||||
foundart = Article.find @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should return nil if `get` is used and the document doesn't exist" do
|
||||
foundart = Article.get 'matt aimonetti'
|
||||
foundart.should be_nil
|
||||
end
|
||||
it "should raise an error if `get!` is used and the document doesn't exist" do
|
||||
lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
|
||||
end
|
||||
it "should raise an error if `find!` is used and the document doesn't exist" do
|
||||
lambda{foundart = Article.find!('matt aimonetti')}.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model with a subobjects array" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 200",
|
||||
"questions" => [
|
||||
{
|
||||
"q" => "Carve the ___ of reality at the ___.",
|
||||
"a" => ["beast","joints"]
|
||||
},{
|
||||
"q" => "Who layed the smack down on Leibniz's Law?",
|
||||
"a" => "Willard Van Orman Quine"
|
||||
}
|
||||
]
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course.title.should == "Metaphysics 200"
|
||||
end
|
||||
it "should instantiate them as such" do
|
||||
@course["questions"][0].a[0].should == "beast"
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding all instances of a model" do
|
||||
before(:all) do
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
|
||||
WithTemplateAndUniqueID.database.bulk_delete
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '4').save
|
||||
end
|
||||
it "should make the design doc" do
|
||||
WithTemplateAndUniqueID.all
|
||||
d = WithTemplateAndUniqueID.design_doc
|
||||
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
||||
end
|
||||
it "should find all" do
|
||||
rs = WithTemplateAndUniqueID.all
|
||||
rs.length.should == 4
|
||||
end
|
||||
end
|
||||
|
||||
describe "counting all instances of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
end
|
||||
|
||||
it ".count should return 0 if there are no docuemtns" do
|
||||
WithTemplateAndUniqueID.count.should == 0
|
||||
end
|
||||
|
||||
it ".count should return the number of documents" do
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
|
||||
WithTemplateAndUniqueID.count.should == 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "finding the first instance of a model" do
|
||||
before(:each) do
|
||||
@db = reset_test_db!
|
||||
# WithTemplateAndUniqueID.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '2').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '3').save
|
||||
WithTemplateAndUniqueID.new('important-field' => '4').save
|
||||
end
|
||||
it "should make the design doc" do
|
||||
WithTemplateAndUniqueID.all
|
||||
d = WithTemplateAndUniqueID.design_doc
|
||||
d['views']['all']['map'].should include('WithTemplateAndUniqueID')
|
||||
end
|
||||
it "should find first" do
|
||||
rs = WithTemplateAndUniqueID.first
|
||||
rs['important-field'].should == "1"
|
||||
end
|
||||
it "should return nil if no instances are found" do
|
||||
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
|
||||
WithTemplateAndUniqueID.first.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "lazily refreshing the design document" do
|
||||
before(:all) do
|
||||
@db = reset_test_db!
|
||||
WithTemplateAndUniqueID.new('important-field' => '1').save
|
||||
end
|
||||
it "should not save the design doc twice" do
|
||||
WithTemplateAndUniqueID.all
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.refresh_design_doc
|
||||
rev = WithTemplateAndUniqueID.design_doc['_rev']
|
||||
WithTemplateAndUniqueID.req_design_doc_refresh
|
||||
WithTemplateAndUniqueID.refresh_design_doc
|
||||
WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model with a subobject field" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 410",
|
||||
"professor" => {
|
||||
"name" => ["Mark", "Hinchliff"]
|
||||
},
|
||||
"ends_at" => "2008/12/19 13:00:00 +0800"
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course["professor"]["name"][1].should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the professor as a person" do
|
||||
@course['professor'].last_name.should == "Hinchliff"
|
||||
end
|
||||
it "should instantiate the ends_at as a Time" do
|
||||
@course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
|
||||
end
|
||||
end
|
||||
|
||||
describe "timestamping" do
|
||||
before(:each) do
|
||||
oldart = Article.get "saving-this" rescue nil
|
||||
oldart.destroy if oldart
|
||||
@art = Article.new(:title => "Saving this")
|
||||
@art.save
|
||||
end
|
||||
|
||||
it "should define the updated_at and created_at getters and set the values" do
|
||||
@obj.save
|
||||
obj = WithDefaultValues.get(@obj.id)
|
||||
obj.should be_an_instance_of(WithDefaultValues)
|
||||
obj.created_at.should be_an_instance_of(Time)
|
||||
obj.updated_at.should be_an_instance_of(Time)
|
||||
obj.created_at.to_s.should == @obj.updated_at.to_s
|
||||
end
|
||||
|
||||
it "should not change created_at on update" do
|
||||
2.times do
|
||||
lambda do
|
||||
@art.save
|
||||
end.should_not change(@art, :created_at)
|
||||
end
|
||||
end
|
||||
|
||||
it "should set the time on create" do
|
||||
(Time.now - @art.created_at).should < 2
|
||||
foundart = Article.get @art.id
|
||||
foundart.created_at.should == foundart.updated_at
|
||||
end
|
||||
it "should set the time on update" do
|
||||
@art.save
|
||||
@art.created_at.should < @art.updated_at
|
||||
end
|
||||
end
|
||||
|
||||
describe "basic saving and retrieving" do
|
||||
it "should work fine" do
|
||||
@obj.name = "should be easily saved and retrieved"
|
||||
@obj.save
|
||||
saved_obj = WithDefaultValues.get(@obj.id)
|
||||
saved_obj.should_not be_nil
|
||||
end
|
||||
|
||||
it "should parse the Time attributes automatically" do
|
||||
@obj.name = "should parse the Time attributes automatically"
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.save
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
saved_obj = WithDefaultValues.get(@obj.id)
|
||||
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model" do
|
||||
before(:all) do
|
||||
@sobj = Basic.new
|
||||
@sobj.save.should == true
|
||||
end
|
||||
|
||||
it "should save the doc" do
|
||||
doc = Basic.get(@sobj.id)
|
||||
doc['_id'].should == @sobj.id
|
||||
end
|
||||
|
||||
it "should be set for resaving" do
|
||||
rev = @obj.rev
|
||||
@sobj['another-key'] = "some value"
|
||||
@sobj.save
|
||||
@sobj.rev.should_not == rev
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@sobj.id.should be_an_instance_of(String)
|
||||
end
|
||||
|
||||
it "should set the type" do
|
||||
@sobj['couchrest-type'].should == 'Basic'
|
||||
end
|
||||
|
||||
describe "save!" do
|
||||
|
||||
before(:each) do
|
||||
@sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
|
||||
end
|
||||
|
||||
it "should return true if save the document" do
|
||||
@sobj.save!.should == true
|
||||
end
|
||||
|
||||
it "should raise error if don't save the document" do
|
||||
@sobj.first_name = nil
|
||||
lambda { @sobj.save!.should == true }.should raise_error(RuntimeError)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id configured" do
|
||||
before(:each) do
|
||||
@art = Article.new
|
||||
@old = Article.database.get('this-is-the-title') rescue nil
|
||||
Article.database.delete_doc(@old) if @old
|
||||
end
|
||||
|
||||
it "should be a new document" do
|
||||
@art.should be_new
|
||||
@art.title.should be_nil
|
||||
end
|
||||
|
||||
it "should require the title" do
|
||||
lambda{@art.save}.should raise_error
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should == true
|
||||
end
|
||||
|
||||
it "should not change the slug on update" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should == true
|
||||
@art.title = 'new title'
|
||||
@art.save.should == true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should raise an error when the slug is taken" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should == true
|
||||
@art2 = Article.new(:title => 'This is the title!')
|
||||
lambda{@art2.save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the slug" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should == true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should == true
|
||||
@art.id.should == 'this-is-the-title'
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id lambda" do
|
||||
before(:each) do
|
||||
@templated = WithTemplateAndUniqueID.new
|
||||
@old = WithTemplateAndUniqueID.get('very-important') rescue nil
|
||||
@old.destroy if @old
|
||||
end
|
||||
|
||||
it "should require the field" do
|
||||
lambda{@templated.save}.should raise_error
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should == true
|
||||
end
|
||||
|
||||
it "should save with the id" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should == true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.should == @templated
|
||||
end
|
||||
|
||||
it "should not change the id on update" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should == true
|
||||
@templated['important-field'] = 'not-important'
|
||||
@templated.save.should == true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.should == @templated
|
||||
end
|
||||
|
||||
it "should raise an error when the id is taken" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should == true
|
||||
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should == true
|
||||
@templated.id.should == 'very-important'
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroying an instance" do
|
||||
before(:each) do
|
||||
@dobj = Basic.new
|
||||
@dobj.save.should == true
|
||||
end
|
||||
it "should return true" do
|
||||
result = @dobj.destroy
|
||||
result.should == true
|
||||
end
|
||||
it "should be resavable" do
|
||||
@dobj.destroy
|
||||
@dobj.rev.should be_nil
|
||||
@dobj.id.should be_nil
|
||||
@dobj.save.should == true
|
||||
end
|
||||
it "should make it go away" do
|
||||
@dobj.destroy
|
||||
lambda{Basic.get!(@dobj.id)}.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "callbacks" do
|
||||
|
||||
before(:each) do
|
||||
@doc = WithCallBacks.new
|
||||
end
|
||||
|
||||
|
||||
describe "validate" do
|
||||
it "should run before_validate before validating" do
|
||||
@doc.run_before_validate.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_before_validate.should be_true
|
||||
end
|
||||
it "should run after_validate after validating" do
|
||||
@doc.run_after_validate.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_after_validate.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "save" do
|
||||
it "should run the after filter after saving" do
|
||||
@doc.run_after_save.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_after_save.should be_true
|
||||
end
|
||||
it "should run the grouped callbacks before saving" do
|
||||
@doc.run_one.should be_nil
|
||||
@doc.run_two.should be_nil
|
||||
@doc.run_three.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_one.should be_true
|
||||
@doc.run_two.should be_true
|
||||
@doc.run_three.should be_true
|
||||
end
|
||||
it "should not run conditional callbacks" do
|
||||
@doc.run_it = false
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_nil
|
||||
@doc.conditional_two.should be_nil
|
||||
end
|
||||
it "should run conditional callbacks" do
|
||||
@doc.run_it = true
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_true
|
||||
@doc.conditional_two.should be_true
|
||||
end
|
||||
end
|
||||
describe "create" do
|
||||
it "should run the before save filter when creating" do
|
||||
@doc.run_before_save.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.run_before_save.should be_true
|
||||
end
|
||||
it "should run the before create filter" do
|
||||
@doc.run_before_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_before_create.should be_true
|
||||
end
|
||||
it "should run the after create filter" do
|
||||
@doc.run_after_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_after_create.should be_true
|
||||
end
|
||||
end
|
||||
describe "update" do
|
||||
|
||||
before(:each) do
|
||||
@doc.save
|
||||
end
|
||||
it "should run the before update filter when updating an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
it "should run the after update filter when updating an existing document" do
|
||||
@doc.run_after_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_after_update.should be_true
|
||||
end
|
||||
it "should run the before update filter when saving an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.save
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "getter and setter methods" do
|
||||
it "should try to call the arg= method before setting :arg in the hash" do
|
||||
@doc = WithGetterAndSetterMethods.new(:arg => "foo")
|
||||
@doc['arg'].should be_nil
|
||||
@doc[:arg].should be_nil
|
||||
@doc.other_arg.should == "foo-foo"
|
||||
end
|
||||
end
|
||||
|
||||
describe "initialization" do
|
||||
it "should call after_initialize method if available" do
|
||||
@doc = WithAfterInitializeMethod.new
|
||||
@doc['some_value'].should eql('value')
|
||||
end
|
||||
end
|
||||
|
||||
describe "recursive validation on an extended document" do
|
||||
before :each do
|
||||
reset_test_db!
|
||||
@cat = Cat.new(:name => 'Sockington')
|
||||
end
|
||||
|
||||
it "should not save if a nested casted model is invalid" do
|
||||
@cat.favorite_toy = CatToy.new
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested casted model is valid" do
|
||||
@cat.favorite_toy = CatToy.new(:name => 'Squeaky')
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not save when nested collection contains an invalid casted model" do
|
||||
@cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
|
||||
@cat.should_not be_valid
|
||||
@cat.save.should be_false
|
||||
lambda{@cat.save!}.should raise_error
|
||||
end
|
||||
|
||||
it "should save when nested collection contains valid casted models" do
|
||||
@cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
|
||||
@cat.should be_valid
|
||||
@cat.save.should be_true
|
||||
lambda{@cat.save!}.should_not raise_error
|
||||
end
|
||||
|
||||
it "should not fail if the nested casted model doesn't have validation" do
|
||||
Cat.property :trainer, :cast_as => 'Person'
|
||||
Cat.validates_presence_of :name
|
||||
cat = Cat.new(:name => 'Mr Bigglesworth')
|
||||
cat.trainer = Person.new
|
||||
cat.trainer.validatable?.should be_false
|
||||
cat.should be_valid
|
||||
cat.save.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "searching the contents of an extended document" do
|
||||
before :each do
|
||||
@db = reset_test_db!
|
||||
|
||||
names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
|
||||
names.each { |name| Cat.create(:name => name) }
|
||||
|
||||
search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
|
||||
'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
|
||||
@db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
|
||||
end
|
||||
|
||||
it "should be able to paginate through a large set of search results" do
|
||||
if couchdb_lucene_available?
|
||||
names = []
|
||||
Cat.paginated_each(:design_doc => "search", :view_name => "cats",
|
||||
:q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
|
||||
cat.should_not be_nil
|
||||
names << cat.name
|
||||
end
|
||||
|
||||
names.size.should == 5
|
||||
names.should include('Sockington')
|
||||
names.should include('Smitty')
|
||||
names.should include('Sammy')
|
||||
names.should include('Samson')
|
||||
names.should include('Simon')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -13,7 +13,7 @@ begin
|
|||
class PlainChild < PlainParent
|
||||
end
|
||||
|
||||
class ExtendedParent < CouchRest::ExtendedDocument
|
||||
class ExtendedParent < CouchRest::Model::Base
|
||||
class_inheritable_accessor :foo
|
||||
self.foo = :bar
|
||||
end
|
409
spec/couchrest/persistence_spec.rb
Normal file
409
spec/couchrest/persistence_spec.rb
Normal file
|
@ -0,0 +1,409 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'base')
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
|
||||
describe "Model Persistence" do
|
||||
|
||||
before(:each) do
|
||||
@obj = WithDefaultValues.new
|
||||
end
|
||||
|
||||
describe "creating a new document from database" do
|
||||
|
||||
it "should instantialize" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize of same class if no couchrest-type included from DB" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
|
||||
doc.class.should eql(Article)
|
||||
end
|
||||
|
||||
it "should instantialize document of different type" do
|
||||
doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => 'WithTemplateAndUniqueID', 'name' => 'my test'})
|
||||
doc.class.should eql(WithTemplateAndUniqueID)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "basic saving and retrieving" do
|
||||
it "should work fine" do
|
||||
@obj.name = "should be easily saved and retrieved"
|
||||
@obj.save
|
||||
saved_obj = WithDefaultValues.get(@obj.id)
|
||||
saved_obj.should_not be_nil
|
||||
end
|
||||
|
||||
it "should parse the Time attributes automatically" do
|
||||
@obj.name = "should parse the Time attributes automatically"
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
@obj.save
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
saved_obj = WithDefaultValues.get(@obj.id)
|
||||
saved_obj.set_by_proc.should be_an_instance_of(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe "creating a model" do
|
||||
|
||||
before(:each) do
|
||||
@sobj = Basic.new
|
||||
end
|
||||
|
||||
it "should accept true or false on save for validation" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(true)
|
||||
end
|
||||
|
||||
it "should accept hash with validation option" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(:validate => true)
|
||||
end
|
||||
|
||||
it "should not call validation when option is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(false)
|
||||
end
|
||||
|
||||
it "should not call validation when option :validate is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(:validate => false)
|
||||
end
|
||||
|
||||
it "should instantialize and save a document" do
|
||||
article = Article.create(:title => 'my test')
|
||||
article.title.should == 'my test'
|
||||
article.should_not be_new
|
||||
end
|
||||
|
||||
it "should trigger the create callbacks" do
|
||||
doc = WithCallBacks.create(:name => 'my other test')
|
||||
doc.run_before_create.should be_true
|
||||
doc.run_after_create.should be_true
|
||||
doc.run_before_save.should be_true
|
||||
doc.run_after_save.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "saving a model" do
|
||||
before(:all) do
|
||||
@sobj = Basic.new
|
||||
@sobj.save.should be_true
|
||||
end
|
||||
|
||||
it "should save the doc" do
|
||||
doc = Basic.get(@sobj.id)
|
||||
doc['_id'].should == @sobj.id
|
||||
end
|
||||
|
||||
it "should be set for resaving" do
|
||||
rev = @obj.rev
|
||||
@sobj['another-key'] = "some value"
|
||||
@sobj.save
|
||||
@sobj.rev.should_not == rev
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@sobj.id.should be_an_instance_of(String)
|
||||
end
|
||||
|
||||
it "should set the type" do
|
||||
@sobj['couchrest-type'].should == 'Basic'
|
||||
end
|
||||
|
||||
it "should accept true or false on save for validation" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(true)
|
||||
end
|
||||
|
||||
it "should accept hash with validation option" do
|
||||
@sobj.should_receive(:valid?)
|
||||
@sobj.save(:validate => true)
|
||||
end
|
||||
|
||||
it "should not call validation when option is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(false)
|
||||
end
|
||||
|
||||
it "should not call validation when option :validate is false" do
|
||||
@sobj.should_not_receive(:valid?)
|
||||
@sobj.save(:validate => false)
|
||||
end
|
||||
|
||||
describe "save!" do
|
||||
|
||||
before(:each) do
|
||||
@sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
|
||||
end
|
||||
|
||||
it "should return true if save the document" do
|
||||
@sobj.save!.should be_true
|
||||
end
|
||||
|
||||
it "should raise error if don't save the document" do
|
||||
@sobj.first_name = nil
|
||||
lambda { @sobj.save! }.should raise_error(CouchRest::Model::Errors::Validations)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id configured" do
|
||||
before(:each) do
|
||||
@art = Article.new
|
||||
@old = Article.database.get('this-is-the-title') rescue nil
|
||||
Article.database.delete_doc(@old) if @old
|
||||
end
|
||||
|
||||
it "should be a new document" do
|
||||
@art.should be_new
|
||||
@art.title.should be_nil
|
||||
end
|
||||
|
||||
it "should require the title" do
|
||||
lambda{@art.save}.should raise_error
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
end
|
||||
|
||||
it "should not change the slug on update" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.title = 'new title'
|
||||
@art.save.should be_true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should raise an error when the slug is taken" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art2 = Article.new(:title => 'This is the title!')
|
||||
lambda{@art2.save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the slug" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.slug.should == 'this-is-the-title'
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@art.title = 'This is the title'
|
||||
@art.save.should be_true
|
||||
@art.id.should == 'this-is-the-title'
|
||||
end
|
||||
end
|
||||
|
||||
describe "saving a model with a unique_id lambda" do
|
||||
before(:each) do
|
||||
@templated = WithTemplateAndUniqueID.new
|
||||
@old = WithTemplateAndUniqueID.get('very-important') rescue nil
|
||||
@old.destroy if @old
|
||||
end
|
||||
|
||||
it "should require the field" do
|
||||
lambda{@templated.save}.should raise_error
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
end
|
||||
|
||||
it "should save with the id" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.should == @templated
|
||||
end
|
||||
|
||||
it "should not change the id on update" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
@templated['important-field'] = 'not-important'
|
||||
@templated.save.should be_true
|
||||
t = WithTemplateAndUniqueID.get('very-important')
|
||||
t.should == @templated
|
||||
end
|
||||
|
||||
it "should raise an error when the id is taken" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
|
||||
end
|
||||
|
||||
it "should set the id" do
|
||||
@templated['important-field'] = 'very-important'
|
||||
@templated.save.should be_true
|
||||
@templated.id.should == 'very-important'
|
||||
end
|
||||
end
|
||||
|
||||
describe "destroying an instance" do
|
||||
before(:each) do
|
||||
@dobj = Basic.new
|
||||
@dobj.save.should be_true
|
||||
end
|
||||
it "should return true" do
|
||||
result = @dobj.destroy
|
||||
result.should be_true
|
||||
end
|
||||
it "should be resavable" do
|
||||
@dobj.destroy
|
||||
@dobj.rev.should be_nil
|
||||
@dobj.id.should be_nil
|
||||
@dobj.save.should be_true
|
||||
end
|
||||
it "should make it go away" do
|
||||
@dobj.destroy
|
||||
lambda{Basic.get!(@dobj.id)}.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "getting a model" do
|
||||
before(:all) do
|
||||
@art = Article.new(:title => 'All About Getting')
|
||||
@art.save
|
||||
end
|
||||
it "should load and instantiate it" do
|
||||
foundart = Article.get @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should load and instantiate with find" do
|
||||
foundart = Article.find @art.id
|
||||
foundart.title.should == "All About Getting"
|
||||
end
|
||||
it "should return nil if `get` is used and the document doesn't exist" do
|
||||
foundart = Article.get 'matt aimonetti'
|
||||
foundart.should be_nil
|
||||
end
|
||||
it "should raise an error if `get!` is used and the document doesn't exist" do
|
||||
lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
|
||||
end
|
||||
it "should raise an error if `find!` is used and the document doesn't exist" do
|
||||
lambda{foundart = Article.find!('matt aimonetti')}.should raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting a model with a subobjects array" do
|
||||
before(:all) do
|
||||
course_doc = {
|
||||
"title" => "Metaphysics 200",
|
||||
"questions" => [
|
||||
{
|
||||
"q" => "Carve the ___ of reality at the ___.",
|
||||
"a" => ["beast","joints"]
|
||||
},{
|
||||
"q" => "Who layed the smack down on Leibniz's Law?",
|
||||
"a" => "Willard Van Orman Quine"
|
||||
}
|
||||
]
|
||||
}
|
||||
r = Course.database.save_doc course_doc
|
||||
@course = Course.get r['id']
|
||||
end
|
||||
it "should load the course" do
|
||||
@course.title.should == "Metaphysics 200"
|
||||
end
|
||||
it "should instantiate them as such" do
|
||||
@course["questions"][0].a[0].should == "beast"
|
||||
end
|
||||
end
|
||||
|
||||
describe "callbacks" do
|
||||
|
||||
before(:each) do
|
||||
@doc = WithCallBacks.new
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
it "should run before_validation before validating" do
|
||||
@doc.run_before_validate.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_before_validate.should be_true
|
||||
end
|
||||
it "should run after_validation after validating" do
|
||||
@doc.run_after_validate.should be_nil
|
||||
@doc.should be_valid
|
||||
@doc.run_after_validate.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "save" do
|
||||
it "should run the after filter after saving" do
|
||||
@doc.run_after_save.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_after_save.should be_true
|
||||
end
|
||||
it "should run the grouped callbacks before saving" do
|
||||
@doc.run_one.should be_nil
|
||||
@doc.run_two.should be_nil
|
||||
@doc.run_three.should be_nil
|
||||
@doc.save.should be_true
|
||||
@doc.run_one.should be_true
|
||||
@doc.run_two.should be_true
|
||||
@doc.run_three.should be_true
|
||||
end
|
||||
it "should not run conditional callbacks" do
|
||||
@doc.run_it = false
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_nil
|
||||
@doc.conditional_two.should be_nil
|
||||
end
|
||||
it "should run conditional callbacks" do
|
||||
@doc.run_it = true
|
||||
@doc.save.should be_true
|
||||
@doc.conditional_one.should be_true
|
||||
@doc.conditional_two.should be_true
|
||||
end
|
||||
end
|
||||
describe "create" do
|
||||
it "should run the before save filter when creating" do
|
||||
@doc.run_before_save.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.run_before_save.should be_true
|
||||
end
|
||||
it "should run the before create filter" do
|
||||
@doc.run_before_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_before_create.should be_true
|
||||
end
|
||||
it "should run the after create filter" do
|
||||
@doc.run_after_create.should be_nil
|
||||
@doc.create.should_not be_nil
|
||||
@doc.create
|
||||
@doc.run_after_create.should be_true
|
||||
end
|
||||
end
|
||||
describe "update" do
|
||||
|
||||
before(:each) do
|
||||
@doc.save
|
||||
end
|
||||
it "should run the before update filter when updating an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
it "should run the after update filter when updating an existing document" do
|
||||
@doc.run_after_update.should be_nil
|
||||
@doc.update
|
||||
@doc.run_after_update.should be_true
|
||||
end
|
||||
it "should run the before update filter when saving an existing document" do
|
||||
@doc.run_before_update.should be_nil
|
||||
@doc.save
|
||||
@doc.run_before_update.should be_true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
|
@ -1,16 +1,16 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
||||
require File.join(FIXTURE_PATH, 'more', 'service')
|
||||
require File.join(FIXTURE_PATH, 'more', 'event')
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'user')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
||||
|
||||
describe "ExtendedDocument properties" do
|
||||
describe "Model properties" do
|
||||
|
||||
before(:each) do
|
||||
reset_test_db!
|
||||
|
@ -134,27 +134,27 @@ describe "ExtendedDocument properties" do
|
|||
@card.first_name = nil
|
||||
@card.should_not be_valid
|
||||
@card.errors.should_not be_empty
|
||||
@card.errors.on(:first_name).should == ["First name must not be blank"]
|
||||
@card.errors[:first_name].should == ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should let you look up errors for a field by a string name" do
|
||||
@card.first_name = nil
|
||||
@card.should_not be_valid
|
||||
@card.errors.on('first_name').should == ["First name must not be blank"]
|
||||
@card.errors['first_name'].should == ["can't be blank"]
|
||||
end
|
||||
|
||||
it "should validate the presence of 2 attributes" do
|
||||
@invoice.clear
|
||||
@invoice.should_not be_valid
|
||||
@invoice.errors.should_not be_empty
|
||||
@invoice.errors.on(:client_name).first.should == "Client name must not be blank"
|
||||
@invoice.errors.on(:employee_name).should_not be_empty
|
||||
@invoice.errors[:client_name].should == ["can't be blank"]
|
||||
@invoice.errors[:employee_name].should_not be_empty
|
||||
end
|
||||
|
||||
it "should let you set an error message" do
|
||||
@invoice.location = nil
|
||||
@invoice.valid?
|
||||
@invoice.errors.on(:location).should == ["Hey stupid!, you forgot the location"]
|
||||
@invoice.errors[:location].should == ["Hey stupid!, you forgot the location"]
|
||||
end
|
||||
|
||||
it "should validate before saving" do
|
||||
|
@ -165,37 +165,6 @@ describe "ExtendedDocument properties" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "autovalidation" do
|
||||
before(:each) do
|
||||
@service = Service.new(:name => "Coumpound analysis", :price => 3_000)
|
||||
end
|
||||
|
||||
it "should be valid" do
|
||||
@service.should be_valid
|
||||
end
|
||||
|
||||
it "should not respond to properties not setup" do
|
||||
@service.respond_to?(:client_name).should be_false
|
||||
end
|
||||
|
||||
describe "property :name, :length => 4...20" do
|
||||
|
||||
it "should autovalidate the presence when length is set" do
|
||||
@service.name = nil
|
||||
@service.should_not be_valid
|
||||
@service.errors.should_not be_nil
|
||||
@service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
|
||||
end
|
||||
|
||||
it "should autovalidate the correct length" do
|
||||
@service.name = "a"
|
||||
@service.should_not be_valid
|
||||
@service.errors.should_not be_nil
|
||||
@service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "casting" do
|
||||
before(:each) do
|
||||
@course = Course.new(:title => 'Relaxation')
|
||||
|
@ -740,50 +709,43 @@ end
|
|||
describe "Property Class" do
|
||||
|
||||
it "should provide name as string" do
|
||||
property = CouchRest::Property.new(:test, String)
|
||||
property = CouchRest::Model::Property.new(:test, String)
|
||||
property.name.should eql('test')
|
||||
property.to_s.should eql('test')
|
||||
end
|
||||
|
||||
it "should provide class from type" do
|
||||
property = CouchRest::Property.new(:test, String)
|
||||
property = CouchRest::Model::Property.new(:test, String)
|
||||
property.type_class.should eql(String)
|
||||
end
|
||||
|
||||
it "should provide base class from type in array" do
|
||||
property = CouchRest::Property.new(:test, [String])
|
||||
property = CouchRest::Model::Property.new(:test, [String])
|
||||
property.type_class.should eql(String)
|
||||
end
|
||||
|
||||
it "should leave type as string if requested" do
|
||||
property = CouchRest::Property.new(:test, 'String')
|
||||
property.type.should eql('String')
|
||||
property.type_class.should eql(String)
|
||||
it "should raise error if type as string requested" do
|
||||
lambda {
|
||||
property = CouchRest::Model::Property.new(:test, 'String')
|
||||
}.should raise_error
|
||||
end
|
||||
|
||||
it "should leave type nil and return string by default" do
|
||||
property = CouchRest::Property.new(:test, nil)
|
||||
it "should leave type nil and return class as nil also" do
|
||||
property = CouchRest::Model::Property.new(:test, nil)
|
||||
property.type.should be_nil
|
||||
# Type cast should never be used on non-casted property!
|
||||
property.type_class.should eql(String)
|
||||
property.type_class.should be_nil
|
||||
end
|
||||
|
||||
it "should convert empty type array to [String]" do
|
||||
property = CouchRest::Property.new(:test, [])
|
||||
property.type_class.should eql(String)
|
||||
end
|
||||
|
||||
it "should convert boolean text-type TrueClass" do
|
||||
property = CouchRest::Property.new(:test, 'boolean')
|
||||
property.type.should eql('boolean') # no change
|
||||
property.type_class.should eql(TrueClass)
|
||||
it "should convert empty type array to [Object]" do
|
||||
property = CouchRest::Model::Property.new(:test, [])
|
||||
property.type_class.should eql(Object)
|
||||
end
|
||||
|
||||
it "should set init method option or leave as 'new'" do
|
||||
# (bad example! Time already typecast)
|
||||
property = CouchRest::Property.new(:test, Time)
|
||||
property = CouchRest::Model::Property.new(:test, Time)
|
||||
property.init_method.should eql('new')
|
||||
property = CouchRest::Property.new(:test, Time, :init_method => 'parse')
|
||||
property = CouchRest::Model::Property.new(:test, Time, :init_method => 'parse')
|
||||
property.init_method.should eql('parse')
|
||||
end
|
||||
|
||||
|
@ -791,32 +753,32 @@ describe "Property Class" do
|
|||
|
||||
describe "casting" do
|
||||
it "should cast a value" do
|
||||
property = CouchRest::Property.new(:test, Date)
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
||||
property.cast_value(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
|
||||
end
|
||||
|
||||
it "should cast an array of values" do
|
||||
property = CouchRest::Property.new(:test, [Date])
|
||||
property = CouchRest::Model::Property.new(:test, [Date])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).should eql([Date.new(2010, 6, 1), Date.new(2010, 6, 2)])
|
||||
end
|
||||
|
||||
it "should set a CastedArray on array of Objects" do
|
||||
property = CouchRest::Property.new(:test, [Object])
|
||||
property = CouchRest::Model::Property.new(:test, [Object])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(::CouchRest::CastedArray)
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
||||
end
|
||||
|
||||
it "should not set a CastedArray on array of Strings" do
|
||||
property = CouchRest::Property.new(:test, [String])
|
||||
property = CouchRest::Model::Property.new(:test, [String])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should_not eql(::CouchRest::CastedArray)
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should_not eql(CouchRest::Model::CastedArray)
|
||||
end
|
||||
|
||||
it "should raise and error if value is array when type is not" do
|
||||
property = CouchRest::Property.new(:test, Date)
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
parent = mock("FooClass")
|
||||
lambda {
|
||||
cast = property.cast(parent, [Date.new(2010, 6, 1)])
|
||||
|
@ -825,13 +787,13 @@ describe "Property Class" do
|
|||
|
||||
|
||||
it "should set parent as casted_by object in CastedArray" do
|
||||
property = CouchRest::Property.new(:test, [Object])
|
||||
property = CouchRest::Model::Property.new(:test, [Object])
|
||||
parent = mock("FooObject")
|
||||
property.cast(parent, ["2010-06-01", "2010-06-02"]).casted_by.should eql(parent)
|
||||
end
|
||||
|
||||
it "should set casted_by on new value" do
|
||||
property = CouchRest::Property.new(:test, CatToy)
|
||||
property = CouchRest::Model::Property.new(:test, CatToy)
|
||||
parent = mock("CatObject")
|
||||
cast = property.cast(parent, {:name => 'catnip'})
|
||||
cast.casted_by.should eql(parent)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
@ -9,6 +10,9 @@ Card.property :bg_color, :default => '#ccc'
|
|||
class BusinessCard < Card
|
||||
property :extension_code
|
||||
property :job_title
|
||||
|
||||
validates_presence_of :extension_code
|
||||
validates_presence_of :job_title
|
||||
end
|
||||
|
||||
class DesignBusinessCard < BusinessCard
|
||||
|
@ -20,7 +24,7 @@ class OnlineCourse < Course
|
|||
view_by :url
|
||||
end
|
||||
|
||||
class Animal < CouchRest::ExtendedDocument
|
||||
class Animal < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
view_by :name
|
||||
|
@ -28,7 +32,7 @@ end
|
|||
|
||||
class Dog < Animal; end
|
||||
|
||||
describe "Subclassing an ExtendedDocument" do
|
||||
describe "Subclassing a Model" do
|
||||
|
||||
before(:each) do
|
||||
@card = BusinessCard.new
|
||||
|
@ -42,22 +46,18 @@ describe "Subclassing an ExtendedDocument" do
|
|||
@card.database.uri.should == Card.database.uri
|
||||
end
|
||||
|
||||
it "should share the same autovalidation details" do
|
||||
@card.auto_validation.should be_true
|
||||
end
|
||||
|
||||
it "should have kept the validation details" do
|
||||
@card.should_not be_valid
|
||||
end
|
||||
|
||||
it "should have added the new validation details" do
|
||||
validated_fields = @card.class.validators.contexts[:default].map{|v| v.field_name}
|
||||
validated_fields.should include(:extension_code)
|
||||
validated_fields = @card.class.validators.map{|v| v.attributes}.flatten
|
||||
validated_fields.should include(:extension_code)
|
||||
validated_fields.should include(:job_title)
|
||||
end
|
||||
|
||||
it "should not add to the parent's validations" do
|
||||
validated_fields = Card.validators.contexts[:default].map{|v| v.field_name}
|
||||
validated_fields = Card.validators.map{|v| v.attributes}.flatten
|
||||
validated_fields.should_not include(:extension_code)
|
||||
validated_fields.should_not include(:job_title)
|
||||
end
|
|
@ -1,10 +1,12 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'article')
|
||||
require File.join(FIXTURE_PATH, 'more', 'course')
|
||||
|
||||
describe "ExtendedDocument views" do
|
||||
describe "Model views" do
|
||||
|
||||
class Unattached < CouchRest::ExtendedDocument
|
||||
class Unattached < CouchRest::Model::Base
|
||||
# Note: no use_database here
|
||||
property :title
|
||||
property :questions
|
||||
|
@ -438,7 +440,7 @@ describe "ExtendedDocument views" do
|
|||
it "should pass database parameter to pager" do
|
||||
proxy = mock(:proxy)
|
||||
proxy.stub!(:paginate)
|
||||
::CouchRest::Mixins::Collection::CollectionProxy.should_receive(:new).with('database', anything(), anything(), anything(), anything()).and_return(proxy)
|
||||
::CouchRest::Model::Collection::CollectionProxy.should_receive(:new).with('database', anything(), anything(), anything(), anything()).and_return(proxy)
|
||||
Article.paginate(:design_doc => 'Article', :view_name => 'by_date', :database => 'database')
|
||||
end
|
||||
end
|
117
spec/fixtures/base.rb
vendored
Normal file
117
spec/fixtures/base.rb
vendored
Normal file
|
@ -0,0 +1,117 @@
|
|||
class WithDefaultValues < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :preset, Object, :default => {:right => 10, :top_align => false}
|
||||
property :set_by_proc, Time, :default => Proc.new{Time.now}
|
||||
property :tags, [String], :default => []
|
||||
property :read_only_with_default, :default => 'generic', :read_only => true
|
||||
property :default_false, TrueClass, :default => false
|
||||
property :name
|
||||
timestamps!
|
||||
end
|
||||
|
||||
class WithSimplePropertyType < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name, String
|
||||
property :preset, String, :default => 'none'
|
||||
property :tags, [String]
|
||||
timestamps!
|
||||
end
|
||||
|
||||
class WithCallBacks < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
property :name
|
||||
property :run_before_validate
|
||||
property :run_after_validate
|
||||
property :run_before_save
|
||||
property :run_after_save
|
||||
property :run_before_create
|
||||
property :run_after_create
|
||||
property :run_before_update
|
||||
property :run_after_update
|
||||
|
||||
before_validate do |object|
|
||||
object.run_before_validate = true
|
||||
end
|
||||
after_validate do |object|
|
||||
object.run_after_validate = true
|
||||
end
|
||||
before_save do |object|
|
||||
object.run_before_save = true
|
||||
end
|
||||
after_save do |object|
|
||||
object.run_after_save = true
|
||||
end
|
||||
before_create do |object|
|
||||
object.run_before_create = true
|
||||
end
|
||||
after_create do |object|
|
||||
object.run_after_create = true
|
||||
end
|
||||
before_update do |object|
|
||||
object.run_before_update = true
|
||||
end
|
||||
after_update do |object|
|
||||
object.run_after_update = true
|
||||
end
|
||||
|
||||
property :run_one
|
||||
property :run_two
|
||||
property :run_three
|
||||
|
||||
before_save :run_one_method, :run_two_method do |object|
|
||||
object.run_three = true
|
||||
end
|
||||
def run_one_method
|
||||
self.run_one = true
|
||||
end
|
||||
def run_two_method
|
||||
self.run_two = true
|
||||
end
|
||||
|
||||
attr_accessor :run_it
|
||||
property :conditional_one
|
||||
property :conditional_two
|
||||
|
||||
before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
|
||||
def conditional_one_method
|
||||
self.conditional_one = true
|
||||
end
|
||||
def conditional_two_method
|
||||
self.conditional_two = true
|
||||
end
|
||||
end
|
||||
|
||||
class WithTemplateAndUniqueID < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
unique_id do |model|
|
||||
model['important-field']
|
||||
end
|
||||
property :preset, :default => 'value'
|
||||
property :has_no_default
|
||||
end
|
||||
|
||||
class WithGetterAndSetterMethods < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :other_arg
|
||||
def arg
|
||||
other_arg
|
||||
end
|
||||
|
||||
def arg=(value)
|
||||
self.other_arg = "foo-#{value}"
|
||||
end
|
||||
end
|
||||
|
||||
class WithAfterInitializeMethod < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :some_value
|
||||
|
||||
def after_initialize
|
||||
self.some_value ||= "value"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
6
spec/fixtures/more/article.rb
vendored
6
spec/fixtures/more/article.rb
vendored
|
@ -1,4 +1,4 @@
|
|||
class Article < CouchRest::ExtendedDocument
|
||||
class Article < CouchRest::Model::Base
|
||||
use_database DB
|
||||
unique_id :slug
|
||||
|
||||
|
@ -20,10 +20,10 @@ class Article < CouchRest::ExtendedDocument
|
|||
return sum(values);
|
||||
}"
|
||||
|
||||
property :date, :type => 'Date'
|
||||
property :date, Date
|
||||
property :slug, :read_only => true
|
||||
property :title
|
||||
property :tags, :type => ['String']
|
||||
property :tags, [String]
|
||||
|
||||
timestamps!
|
||||
|
||||
|
|
7
spec/fixtures/more/card.rb
vendored
7
spec/fixtures/more/card.rb
vendored
|
@ -1,9 +1,4 @@
|
|||
class Card < CouchRest::ExtendedDocument
|
||||
# Include the validation module to get access to the validation methods
|
||||
include CouchRest::Validation
|
||||
# set the auto_validation before defining the properties
|
||||
auto_validate!
|
||||
|
||||
class Card < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
|
||||
|
|
7
spec/fixtures/more/cat.rb
vendored
7
spec/fixtures/more/cat.rb
vendored
|
@ -1,16 +1,13 @@
|
|||
|
||||
class CatToy < Hash
|
||||
include ::CouchRest::CastedModel
|
||||
include ::CouchRest::Validation
|
||||
include ::CouchRest::Model::CastedModel
|
||||
|
||||
property :name
|
||||
|
||||
validates_presence_of :name
|
||||
end
|
||||
|
||||
class Cat < CouchRest::ExtendedDocument
|
||||
include ::CouchRest::Validation
|
||||
|
||||
class Cat < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
|
||||
|
|
26
spec/fixtures/more/course.rb
vendored
26
spec/fixtures/more/course.rb
vendored
|
@ -1,22 +1,22 @@
|
|||
require File.join(FIXTURE_PATH, 'more', 'question')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
|
||||
class Course < CouchRest::ExtendedDocument
|
||||
class Course < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
property :title, :cast_as => 'String'
|
||||
property :questions, :cast_as => ['Question']
|
||||
property :professor, :cast_as => 'Person'
|
||||
property :participants, :type => ['Object']
|
||||
property :ends_at, :type => 'Time'
|
||||
property :estimate, :type => 'Float'
|
||||
property :hours, :type => 'Integer'
|
||||
property :profit, :type => 'BigDecimal'
|
||||
property :started_on, :type => 'Date'
|
||||
property :updated_at, :type => 'DateTime'
|
||||
property :active, :type => 'Boolean'
|
||||
property :title, String
|
||||
property :questions, [Question]
|
||||
property :professor, Person
|
||||
property :participants, [Object]
|
||||
property :ends_at, Time
|
||||
property :estimate, Float
|
||||
property :hours, Integer
|
||||
property :profit, BigDecimal
|
||||
property :started_on, :type => Date
|
||||
property :updated_at, :type => DateTime
|
||||
property :active, :type => TrueClass
|
||||
property :very_active, :type => TrueClass
|
||||
property :klass, :type => 'Class'
|
||||
property :klass, :type => Class
|
||||
|
||||
view_by :title
|
||||
view_by :title, :active
|
||||
|
|
6
spec/fixtures/more/event.rb
vendored
6
spec/fixtures/more/event.rb
vendored
|
@ -1,8 +1,8 @@
|
|||
class Event < CouchRest::ExtendedDocument
|
||||
class Event < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
property :subject
|
||||
property :occurs_at, :cast_as => 'Time', :init_method => 'parse'
|
||||
property :end_date, :cast_as => 'Date', :init_method => 'parse'
|
||||
property :occurs_at, Time, :init_method => 'parse'
|
||||
property :end_date, Date, :init_method => 'parse'
|
||||
|
||||
end
|
||||
|
|
5
spec/fixtures/more/invoice.rb
vendored
5
spec/fixtures/more/invoice.rb
vendored
|
@ -1,7 +1,4 @@
|
|||
class Invoice < CouchRest::ExtendedDocument
|
||||
# Include the validation module to get access to the validation methods
|
||||
include CouchRest::Validation
|
||||
|
||||
class Invoice < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
|
||||
|
|
4
spec/fixtures/more/person.rb
vendored
4
spec/fixtures/more/person.rb
vendored
|
@ -1,6 +1,6 @@
|
|||
class Person < Hash
|
||||
include ::CouchRest::CastedModel
|
||||
property :pet, :cast_as => 'Cat'
|
||||
include ::CouchRest::Model::CastedModel
|
||||
property :pet, Cat
|
||||
property :name, [String]
|
||||
|
||||
def last_name
|
||||
|
|
2
spec/fixtures/more/question.rb
vendored
2
spec/fixtures/more/question.rb
vendored
|
@ -1,5 +1,5 @@
|
|||
class Question < Hash
|
||||
include ::CouchRest::CastedModel
|
||||
include ::CouchRest::Model::CastedModel
|
||||
|
||||
property :q
|
||||
property :a
|
||||
|
|
12
spec/fixtures/more/service.rb
vendored
12
spec/fixtures/more/service.rb
vendored
|
@ -1,12 +1,10 @@
|
|||
class Service < CouchRest::ExtendedDocument
|
||||
# Include the validation module to get access to the validation methods
|
||||
include CouchRest::Validation
|
||||
auto_validate!
|
||||
class Service < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
|
||||
# Official Schema
|
||||
property :name, :length => 4...20
|
||||
property :price, :type => 'Integer'
|
||||
property :name
|
||||
property :price, Integer
|
||||
|
||||
end
|
||||
validates_length_of :name, :minimum => 4, :maximum => 20
|
||||
end
|
||||
|
|
4
spec/fixtures/more/user.rb
vendored
4
spec/fixtures/more/user.rb
vendored
|
@ -1,11 +1,11 @@
|
|||
class User < CouchRest::ExtendedDocument
|
||||
class User < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
property :name, :accessible => true
|
||||
property :admin # this will be automatically protected
|
||||
end
|
||||
|
||||
class SpecialUser < CouchRest::ExtendedDocument
|
||||
class SpecialUser < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
||||
property :name # this will not be protected
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require "rubygems"
|
||||
require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
|
||||
|
||||
require File.join(File.dirname(__FILE__), '..','lib','couchrest_extended_document')
|
||||
require File.join(File.dirname(__FILE__), '..','lib','couchrest_model')
|
||||
# check the following file to see how to use the spec'd features.
|
||||
|
||||
unless defined?(FIXTURE_PATH)
|
||||
|
@ -9,14 +9,13 @@ unless defined?(FIXTURE_PATH)
|
|||
SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp')
|
||||
|
||||
COUCHHOST = "http://127.0.0.1:5984"
|
||||
TESTDB = 'couchrest-test'
|
||||
REPLICATIONDB = 'couchrest-test-replication'
|
||||
TESTDB = 'couchrest-model-test'
|
||||
TEST_SERVER = CouchRest.new
|
||||
TEST_SERVER.default_database = TESTDB
|
||||
DB = TEST_SERVER.database(TESTDB)
|
||||
end
|
||||
|
||||
class Basic < CouchRest::ExtendedDocument
|
||||
class Basic < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue