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.improve_associations
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
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Question < Hash
|
||||
include ::CouchRest::CastedModel
|
||||
include ::CouchRest::Model::CastedModel
|
||||
|
||||
property :q
|
||||
property :a
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 New Issue