added autovalidation (auto_validate! in your ExtendedDocument) and extracted some extlib stuff so we will soon be able to remove the dependency.
This commit is contained in:
parent
e9930c5a86
commit
890b60cae4
18 changed files with 497 additions and 52 deletions
|
@ -1,18 +1,6 @@
|
||||||
module CouchRest
|
require 'delegate'
|
||||||
class Response < Hash
|
|
||||||
def initialize(keys = {})
|
|
||||||
keys.each do |k,v|
|
|
||||||
self[k.to_s] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
def []= key, value
|
|
||||||
super(key.to_s, value)
|
|
||||||
end
|
|
||||||
def [] key
|
|
||||||
super(key.to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
module CouchRest
|
||||||
class Document < Response
|
class Document < Response
|
||||||
include CouchRest::Mixins::Attachments
|
include CouchRest::Mixins::Attachments
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||||
|
|
||||||
# Extracted from ActiveSupport::Callbacks written by Yehuda Katz
|
# Extracted from ActiveSupport::Callbacks written by Yehuda Katz
|
||||||
# http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb
|
# http://github.com/wycats/rails/raw/18b405f154868204a8f332888871041a7bad95e1/activesupport/lib/active_support/callbacks.rb
|
||||||
|
|
||||||
|
|
|
@ -7,23 +7,27 @@ module CouchRest
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
# Stores the class properties
|
# Stores the class properties
|
||||||
def properties
|
def properties
|
||||||
@@properties ||= []
|
@@properties ||= []
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is not a thread safe operation, if you have to set new properties at runtime
|
|
||||||
# make sure to use a mutex.
|
|
||||||
def property(name, options={})
|
def property(name, options={})
|
||||||
unless properties.map{|p| p.name}.include?(name.to_s)
|
define_property(name, options) unless properties.map{|p| p.name}.include?(name.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# This is not a thread safe operation, if you have to set new properties at runtime
|
||||||
|
# make sure to use a mutex.
|
||||||
|
def define_property(name, options={})
|
||||||
property = CouchRest::Property.new(name, options.delete(:type), options)
|
property = CouchRest::Property.new(name, options.delete(:type), options)
|
||||||
create_property_getter(property)
|
create_property_getter(property)
|
||||||
create_property_setter(property) unless property.read_only == true
|
create_property_setter(property) unless property.read_only == true
|
||||||
properties << property
|
properties << property
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
# defines the getter for the property
|
# defines the getter for the property
|
||||||
def create_property_getter(property)
|
def create_property_getter(property)
|
||||||
meth = property.name
|
meth = property.name
|
||||||
|
|
|
@ -27,10 +27,14 @@ class Object
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require 'pathname'
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||||
|
|
||||||
dir = File.join(Pathname(__FILE__).dirname.expand_path, '..', 'validation')
|
dir = File.join(Pathname(__FILE__).dirname.expand_path, '..', 'validation')
|
||||||
|
|
||||||
require File.join(dir, 'validation_errors')
|
require File.join(dir, 'validation_errors')
|
||||||
require File.join(dir, 'contextual_validators')
|
require File.join(dir, 'contextual_validators')
|
||||||
|
require File.join(dir, 'auto_validate')
|
||||||
|
|
||||||
require File.join(dir, 'validators', 'generic_validator')
|
require File.join(dir, 'validators', 'generic_validator')
|
||||||
require File.join(dir, 'validators', 'required_field_validator')
|
require File.join(dir, 'validators', 'required_field_validator')
|
||||||
|
@ -50,6 +54,13 @@ module CouchRest
|
||||||
save_callback :before, :check_validations
|
save_callback :before, :check_validations
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
|
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||||
|
def self.define_property(name, options={})
|
||||||
|
super
|
||||||
|
auto_generate_validations(properties.last)
|
||||||
|
autovalidation_check = true
|
||||||
|
end
|
||||||
|
RUBY_EVAL
|
||||||
end
|
end
|
||||||
|
|
||||||
# Ensures the object is valid for the context provided, and otherwise
|
# Ensures the object is valid for the context provided, and otherwise
|
||||||
|
@ -136,7 +147,7 @@ module CouchRest
|
||||||
include CouchRest::Validation::ValidatesWithMethod
|
include CouchRest::Validation::ValidatesWithMethod
|
||||||
# include CouchRest::Validation::ValidatesWithBlock
|
# include CouchRest::Validation::ValidatesWithBlock
|
||||||
# include CouchRest::Validation::ValidatesIsUnique
|
# include CouchRest::Validation::ValidatesIsUnique
|
||||||
# include CouchRest::Validation::AutoValidate
|
include CouchRest::Validation::AutoValidate
|
||||||
|
|
||||||
# Return the set of contextual validators or create a new one
|
# Return the set of contextual validators or create a new one
|
||||||
#
|
#
|
||||||
|
|
|
@ -4,7 +4,6 @@ module CouchRest
|
||||||
|
|
||||||
def self.included(base)
|
def self.included(base)
|
||||||
base.extend(ClassMethods)
|
base.extend(ClassMethods)
|
||||||
# extlib is required for the following code
|
|
||||||
base.send(:class_inheritable_accessor, :design_doc)
|
base.send(:class_inheritable_accessor, :design_doc)
|
||||||
base.send(:class_inheritable_accessor, :design_doc_slug_cache)
|
base.send(:class_inheritable_accessor, :design_doc_slug_cache)
|
||||||
base.send(:class_inheritable_accessor, :design_doc_fresh)
|
base.send(:class_inheritable_accessor, :design_doc_fresh)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
require File.join(File.dirname(__FILE__), 'support', 'class')
|
||||||
|
require File.join(File.dirname(__FILE__), 'support', 'blank')
|
||||||
|
|
||||||
# This file must be loaded after the JSON gem and any other library that beats up the Time class.
|
# This file must be loaded after the JSON gem and any other library that beats up the Time class.
|
||||||
class Time
|
class Time
|
||||||
# This date format sorts lexicographically
|
# This date format sorts lexicographically
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
begin
|
||||||
|
require 'extlib'
|
||||||
|
rescue
|
||||||
|
puts "CouchRest::ExtendedDocument still requires extlib (not for much longer). This is left out of the gemspec on purpose."
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
require 'mime/types'
|
require 'mime/types'
|
||||||
require File.join(File.dirname(__FILE__), "property")
|
require File.join(File.dirname(__FILE__), "property")
|
||||||
require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
|
require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
|
||||||
|
@ -6,11 +13,11 @@ module CouchRest
|
||||||
|
|
||||||
# Same as CouchRest::Document but with properties and validations
|
# Same as CouchRest::Document but with properties and validations
|
||||||
class ExtendedDocument < Document
|
class ExtendedDocument < Document
|
||||||
include CouchRest::Mixins::DocumentQueries
|
include CouchRest::Callbacks
|
||||||
include CouchRest::Mixins::DocumentProperties
|
include CouchRest::Mixins::DocumentProperties
|
||||||
|
include CouchRest::Mixins::DocumentQueries
|
||||||
include CouchRest::Mixins::Views
|
include CouchRest::Mixins::Views
|
||||||
include CouchRest::Mixins::DesignDoc
|
include CouchRest::Mixins::DesignDoc
|
||||||
include CouchRest::Callbacks
|
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
define_callbacks :save
|
define_callbacks :save
|
||||||
|
@ -98,6 +105,7 @@ module CouchRest
|
||||||
# Overridden to set the unique ID.
|
# Overridden to set the unique ID.
|
||||||
# Returns a boolean value
|
# Returns a boolean value
|
||||||
def save_without_callbacks(bulk = false)
|
def save_without_callbacks(bulk = false)
|
||||||
|
raise ArgumentError, "a document requires database to be saved to" unless database
|
||||||
set_unique_id if new_document? && self.respond_to?(:set_unique_id)
|
set_unique_id if new_document? && self.respond_to?(:set_unique_id)
|
||||||
result = database.save_doc(self, bulk)
|
result = database.save_doc(self, bulk)
|
||||||
result["ok"] == true
|
result["ok"] == true
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
module CouchRest
|
module CouchRest
|
||||||
|
|
||||||
# Basic attribute support adding getter/setter + validation
|
# Basic attribute support for adding getter/setter + validation
|
||||||
class Property
|
class Property
|
||||||
attr_reader :name, :type, :validation_format, :required, :read_only, :alias
|
attr_reader :name, :type, :read_only, :alias, :options
|
||||||
|
|
||||||
# attribute to define
|
# attribute to define
|
||||||
def initialize(name, type = String, options = {})
|
def initialize(name, type = nil, options = {})
|
||||||
@name = name.to_s
|
@name = name.to_s
|
||||||
@type = type
|
@type = type || String
|
||||||
parse_options(options)
|
parse_options(options)
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
@ -16,10 +16,10 @@ module CouchRest
|
||||||
private
|
private
|
||||||
def parse_options(options)
|
def parse_options(options)
|
||||||
return if options.empty?
|
return if options.empty?
|
||||||
@required = true if (options[:required] && (options[:required] == true))
|
@validation_format = options.delete(:format) if options[:format]
|
||||||
@validation_format = options[:format] if options[:format]
|
@read_only = options.delete(:read_only) if options[:read_only]
|
||||||
@read_only = options[:read_only] if options[:read_only]
|
@alias = options.delete(:alias) if options[:alias]
|
||||||
@alias = options[:alias] if options
|
@options = options
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
42
lib/couchrest/support/blank.rb
Normal file
42
lib/couchrest/support/blank.rb
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# blank? methods for several different class types
|
||||||
|
class Object
|
||||||
|
# Returns true if the object is nil or empty (if applicable)
|
||||||
|
def blank?
|
||||||
|
nil? || (respond_to?(:empty?) && empty?)
|
||||||
|
end
|
||||||
|
end # class Object
|
||||||
|
|
||||||
|
class Numeric
|
||||||
|
# Numerics can't be blank
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end # class Numeric
|
||||||
|
|
||||||
|
class NilClass
|
||||||
|
# Nils are always blank
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end # class NilClass
|
||||||
|
|
||||||
|
class TrueClass
|
||||||
|
# True is not blank.
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end # class TrueClass
|
||||||
|
|
||||||
|
class FalseClass
|
||||||
|
# False is always blank.
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end # class FalseClass
|
||||||
|
|
||||||
|
class String
|
||||||
|
# Strips out whitespace then tests if the string is empty.
|
||||||
|
def blank?
|
||||||
|
strip.empty?
|
||||||
|
end
|
||||||
|
end # class String
|
175
lib/couchrest/support/class.rb
Normal file
175
lib/couchrest/support/class.rb
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
# Copyright (c) 2004-2008 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.
|
||||||
|
|
||||||
|
# Allows attributes to be shared within an inheritance hierarchy, but where
|
||||||
|
# each descendant gets a copy of their parents' attributes, instead of just a
|
||||||
|
# pointer to the same. This means that the child can add elements to, for
|
||||||
|
# example, an array without those additions being shared with either their
|
||||||
|
# parent, siblings, or children, which is unlike the regular class-level
|
||||||
|
# attributes that are shared across the entire hierarchy.
|
||||||
|
class Class
|
||||||
|
# Defines class-level and instance-level attribute reader.
|
||||||
|
#
|
||||||
|
# @param *syms<Array> Array of attributes to define reader for.
|
||||||
|
# @return <Array[#to_s]> List of attributes that were made into cattr_readers
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
#
|
||||||
|
# @todo Is this inconsistent in that it does not allow you to prevent
|
||||||
|
# an instance_reader via :instance_reader => false
|
||||||
|
def cattr_reader(*syms)
|
||||||
|
syms.flatten.each do |sym|
|
||||||
|
next if sym.is_a?(Hash)
|
||||||
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||||
|
unless defined? @@#{sym}
|
||||||
|
@@#{sym} = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.#{sym}
|
||||||
|
@@#{sym}
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{sym}
|
||||||
|
@@#{sym}
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines class-level (and optionally instance-level) attribute writer.
|
||||||
|
#
|
||||||
|
# @param <Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define writer for.
|
||||||
|
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
||||||
|
# @return <Array[#to_s]> List of attributes that were made into cattr_writers
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
def cattr_writer(*syms)
|
||||||
|
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
||||||
|
syms.flatten.each do |sym|
|
||||||
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||||
|
unless defined? @@#{sym}
|
||||||
|
@@#{sym} = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.#{sym}=(obj)
|
||||||
|
@@#{sym} = obj
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
unless options[:instance_writer] == false
|
||||||
|
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
||||||
|
def #{sym}=(obj)
|
||||||
|
@@#{sym} = obj
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines class-level (and optionally instance-level) attribute accessor.
|
||||||
|
#
|
||||||
|
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to define accessor for.
|
||||||
|
# @option syms :instance_writer<Boolean> if true, instance-level attribute writer is defined.
|
||||||
|
# @return <Array[#to_s]> List of attributes that were made into accessors
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
def cattr_accessor(*syms)
|
||||||
|
cattr_reader(*syms)
|
||||||
|
cattr_writer(*syms)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines class-level inheritable attribute reader. Attributes are available to subclasses,
|
||||||
|
# each subclass has a copy of parent's attribute.
|
||||||
|
#
|
||||||
|
# @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
|
||||||
|
# @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
#
|
||||||
|
# @todo Do we want to block instance_reader via :instance_reader => false
|
||||||
|
# @todo It would be preferable that we do something with a Hash passed in
|
||||||
|
# (error out or do the same as other methods above) instead of silently
|
||||||
|
# moving on). In particular, this makes the return value of this function
|
||||||
|
# less useful.
|
||||||
|
def class_inheritable_reader(*ivars)
|
||||||
|
instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
|
||||||
|
|
||||||
|
ivars.each do |ivar|
|
||||||
|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def self.#{ivar}
|
||||||
|
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
|
||||||
|
ivar = superclass.#{ivar}
|
||||||
|
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
|
||||||
|
@#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) && !ivar.is_a?(Symbol) ? ivar.dup : ivar
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
unless instance_reader == false
|
||||||
|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{ivar}
|
||||||
|
self.class.#{ivar}
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines class-level inheritable attribute writer. Attributes are available to subclasses,
|
||||||
|
# each subclass has a copy of parent's attribute.
|
||||||
|
#
|
||||||
|
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
||||||
|
# define inheritable writer for.
|
||||||
|
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
||||||
|
# @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
#
|
||||||
|
# @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
|
||||||
|
# class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
|
||||||
|
def class_inheritable_writer(*ivars)
|
||||||
|
instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash)
|
||||||
|
ivars.each do |ivar|
|
||||||
|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def self.#{ivar}=(obj)
|
||||||
|
@#{ivar} = obj
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
unless instance_writer == false
|
||||||
|
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
|
||||||
|
# each subclass has a copy of parent's attribute.
|
||||||
|
#
|
||||||
|
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
||||||
|
# define inheritable accessor for.
|
||||||
|
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
||||||
|
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
||||||
|
#
|
||||||
|
# @api public
|
||||||
|
def class_inheritable_accessor(*syms)
|
||||||
|
class_inheritable_reader(*syms)
|
||||||
|
class_inheritable_writer(*syms)
|
||||||
|
end
|
||||||
|
end
|
167
lib/couchrest/validation/auto_validate.rb
Normal file
167
lib/couchrest/validation/auto_validate.rb
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
# Ported from dm-migrations
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'support', 'class')
|
||||||
|
|
||||||
|
module CouchRest
|
||||||
|
|
||||||
|
class Property
|
||||||
|
# flag letting us know if we already checked the autovalidation settings
|
||||||
|
attr_accessor :autovalidation_check
|
||||||
|
end
|
||||||
|
|
||||||
|
module Validation
|
||||||
|
module AutoValidate
|
||||||
|
|
||||||
|
# Turn off auto validation by default
|
||||||
|
def auto_validation
|
||||||
|
@@auto_validation ||= false
|
||||||
|
end
|
||||||
|
|
||||||
|
# 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_is_number
|
||||||
|
# validator to be created for the property. integer_only
|
||||||
|
# is set to true
|
||||||
|
#
|
||||||
|
# BigDecimal or 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.options
|
||||||
|
return unless property.autovalidation_check || auto_validation || (property.options && property.options.has_key?(:auto_validation) && property.options[:auto_validation])
|
||||||
|
|
||||||
|
# value is set by the storage system
|
||||||
|
opts = {}
|
||||||
|
opts[:context] = property.options[:validates] if property.options.has_key?(:validates)
|
||||||
|
|
||||||
|
# presence
|
||||||
|
unless opts[:allow_nil]
|
||||||
|
# validates_present property.name, opts
|
||||||
|
validates_present property.name, options_with_message(opts, property, :presence)
|
||||||
|
end
|
||||||
|
|
||||||
|
# length
|
||||||
|
if property.type == 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 property.name, opts
|
||||||
|
p "dude: #{options_with_message(opts, property, :length)}"
|
||||||
|
validates_length 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 Integer == property.type
|
||||||
|
opts[:integer_only] = true
|
||||||
|
# validates_is_number property.name, opts
|
||||||
|
validates_is_number property.name, options_with_message(opts, property, :is_number)
|
||||||
|
elsif Float == property.type
|
||||||
|
opts[:precision] = property.precision
|
||||||
|
opts[:scale] = property.scale
|
||||||
|
# validates_is_number property.name, opts
|
||||||
|
validates_is_number property.name, options_with_message(opts, property, :is_number)
|
||||||
|
end
|
||||||
|
|
||||||
|
# marked the property has checked
|
||||||
|
property.autovalidation_check = true
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end # module AutoValidate
|
||||||
|
end # module Validation
|
||||||
|
end # module CouchRest
|
|
@ -75,7 +75,7 @@ module CouchRest
|
||||||
# @param <Symbol> field_name the name of the field that caused the error
|
# @param <Symbol> field_name the name of the field that caused the error
|
||||||
# @param <String> message the message to add
|
# @param <String> message the message to add
|
||||||
def add(field_name, message)
|
def add(field_name, message)
|
||||||
(errors[field_name] ||= []) << message
|
(errors[field_name.to_sym] ||= []) << message
|
||||||
end
|
end
|
||||||
|
|
||||||
# Collect all errors into a single list.
|
# Collect all errors into a single list.
|
||||||
|
|
|
@ -66,7 +66,7 @@ module CouchRest
|
||||||
# Returns false for other property types.
|
# Returns false for other property types.
|
||||||
# Returns false for non-properties.
|
# Returns false for non-properties.
|
||||||
def boolean_type?(property)
|
def boolean_type?(property)
|
||||||
property ? property.primitive == TrueClass : false
|
property ? property.type == TrueClass : false
|
||||||
end
|
end
|
||||||
|
|
||||||
end # class RequiredFieldValidator
|
end # class RequiredFieldValidator
|
||||||
|
|
|
@ -2,17 +2,17 @@ require File.dirname(__FILE__) + '/../../spec_helper'
|
||||||
|
|
||||||
describe CouchRest::Server do
|
describe CouchRest::Server do
|
||||||
|
|
||||||
before(:all) do
|
|
||||||
@couch = CouchRest::Server.new
|
|
||||||
end
|
|
||||||
|
|
||||||
after(:all) do
|
|
||||||
@couch.available_databases.each do |ref, db|
|
|
||||||
db.delete!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "available databases" do
|
describe "available databases" do
|
||||||
|
before(:each) do
|
||||||
|
@couch = CouchRest::Server.new
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:each) do
|
||||||
|
@couch.available_databases.each do |ref, db|
|
||||||
|
db.delete!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "should let you add more databases" do
|
it "should let you add more databases" do
|
||||||
@couch.available_databases.should be_empty
|
@couch.available_databases.should be_empty
|
||||||
@couch.define_available_database(:default, "cr-server-test-db")
|
@couch.define_available_database(:default, "cr-server-test-db")
|
||||||
|
@ -20,6 +20,7 @@ describe CouchRest::Server do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should verify that a database is available" do
|
it "should verify that a database is available" do
|
||||||
|
@couch.define_available_database(:default, "cr-server-test-db")
|
||||||
@couch.available_database?(:default).should be_true
|
@couch.available_database?(:default).should be_true
|
||||||
@couch.available_database?("cr-server-test-db").should be_true
|
@couch.available_database?("cr-server-test-db").should be_true
|
||||||
@couch.available_database?(:matt).should be_false
|
@couch.available_database?(:matt).should be_false
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
||||||
|
|
||||||
# check the following file to see how to use the spec'd features.
|
|
||||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||||
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
||||||
|
require File.join(FIXTURE_PATH, 'more', 'service')
|
||||||
|
|
||||||
describe "ExtendedDocument properties" do
|
describe "ExtendedDocument properties" do
|
||||||
|
|
||||||
|
@ -37,6 +36,8 @@ describe "ExtendedDocument properties" do
|
||||||
it "should be auto timestamped" do
|
it "should be auto timestamped" do
|
||||||
@card.created_at.should be_nil
|
@card.created_at.should be_nil
|
||||||
@card.updated_at.should be_nil
|
@card.updated_at.should be_nil
|
||||||
|
# :emo:hack for autospec
|
||||||
|
Card.use_database(TEST_SERVER.default_database) if @card.database.nil?
|
||||||
@card.save
|
@card.save
|
||||||
@card.created_at.should_not be_nil
|
@card.created_at.should_not be_nil
|
||||||
@card.updated_at.should_not be_nil
|
@card.updated_at.should_not be_nil
|
||||||
|
@ -49,7 +50,7 @@ describe "ExtendedDocument properties" do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be able to be validated" do
|
it "should be able to be validated" do
|
||||||
@card.should be_valid
|
@card.valid?.should == true
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should let you validate the presence of an attribute" do
|
it "should let you validate the presence of an attribute" do
|
||||||
|
@ -82,4 +83,31 @@ 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
|
||||||
|
|
||||||
|
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 not be blank"
|
||||||
|
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
|
||||||
|
|
||||||
end
|
end
|
2
spec/fixtures/more/card.rb
vendored
2
spec/fixtures/more/card.rb
vendored
|
@ -15,4 +15,6 @@ class Card < CouchRest::ExtendedDocument
|
||||||
# Validation
|
# Validation
|
||||||
validates_present :first_name
|
validates_present :first_name
|
||||||
|
|
||||||
|
auto_validate!
|
||||||
|
|
||||||
end
|
end
|
14
spec/fixtures/more/service.rb
vendored
Normal file
14
spec/fixtures/more/service.rb
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
class Service < CouchRest::ExtendedDocument
|
||||||
|
# Include the validation module to get access to the validation methods
|
||||||
|
include CouchRest::Validation
|
||||||
|
|
||||||
|
# Set the default database to use
|
||||||
|
use_database TEST_SERVER.default_database
|
||||||
|
|
||||||
|
# Official Schema
|
||||||
|
property :name, :length => 4...20
|
||||||
|
property :price, :type => Integer
|
||||||
|
|
||||||
|
auto_validate!
|
||||||
|
|
||||||
|
end
|
|
@ -1,7 +1,8 @@
|
||||||
require "rubygems"
|
require "rubygems"
|
||||||
require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
|
require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
|
||||||
|
|
||||||
require File.join(File.dirname(__FILE__), '/../lib/couchrest')
|
require File.join(File.dirname(__FILE__), '..','lib','couchrest')
|
||||||
|
# check the following file to see how to use the spec'd features.
|
||||||
|
|
||||||
unless defined?(FIXTURE_PATH)
|
unless defined?(FIXTURE_PATH)
|
||||||
FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures')
|
FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures')
|
||||||
|
|
Loading…
Add table
Reference in a new issue