Renaming to CouchRest Model
Refactored basic directory structure. Moved to ActiveSupport for Validations and Callbacks. Cleaned up older code, and removed support for text property types.
This commit is contained in:
parent
9f1eea8d32
commit
c280b3a29b
70 changed files with 1725 additions and 3733 deletions
|
@ -1,261 +0,0 @@
|
|||
|
||||
require File.join(File.dirname(__FILE__), "property")
|
||||
require File.join(File.dirname(__FILE__), "validation")
|
||||
require File.join(File.dirname(__FILE__), 'mixins')
|
||||
|
||||
module CouchRest
|
||||
|
||||
# Same as CouchRest::Document but with properties and validations
|
||||
class ExtendedDocument < Document
|
||||
|
||||
VERSION = "1.0.0.beta6"
|
||||
|
||||
include CouchRest::Mixins::Callbacks
|
||||
include CouchRest::Mixins::DocumentQueries
|
||||
include CouchRest::Mixins::Views
|
||||
include CouchRest::Mixins::DesignDoc
|
||||
include CouchRest::Mixins::ExtendedAttachments
|
||||
include CouchRest::Mixins::ClassProxy
|
||||
include CouchRest::Mixins::Collection
|
||||
include CouchRest::Mixins::AttributeProtection
|
||||
include CouchRest::Mixins::Attributes
|
||||
include CouchRest::Mixins::Associations
|
||||
|
||||
# Including validation here does not work due to the way inheritance is handled.
|
||||
#include CouchRest::Validation
|
||||
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
end
|
||||
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.send(:include, CouchRest::Mixins::Properties)
|
||||
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.properties = self.properties.dup
|
||||
end
|
||||
EOS
|
||||
subclasses << subklass
|
||||
end
|
||||
|
||||
# Accessors
|
||||
attr_accessor :casted_by
|
||||
|
||||
# Callbacks
|
||||
define_callbacks :create, "result == :halt"
|
||||
define_callbacks :save, "result == :halt"
|
||||
define_callbacks :update, "result == :halt"
|
||||
define_callbacks :destroy, "result == :halt"
|
||||
|
||||
# Creates a new instance, bypassing attribute protection
|
||||
#
|
||||
#
|
||||
# ==== Returns
|
||||
# a document instance
|
||||
def self.create_from_database(doc = {})
|
||||
base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
|
||||
base.new(doc, :directly_set_attributes => true)
|
||||
end
|
||||
|
||||
|
||||
# Instantiate a new ExtendedDocument by preparing all properties
|
||||
# using the provided document hash.
|
||||
#
|
||||
# Options supported:
|
||||
#
|
||||
# * :directly_set_attributes: true when data comes directly from database
|
||||
#
|
||||
def initialize(doc = {}, options = {})
|
||||
prepare_all_attributes(doc, options) # defined in CouchRest::Mixins::Attributes
|
||||
super(doc)
|
||||
unless self['_id'] && self['_rev']
|
||||
self['couchrest-type'] = self.class.to_s
|
||||
end
|
||||
after_initialize if respond_to?(:after_initialize)
|
||||
end
|
||||
|
||||
# Defines an instance and save it directly to the database
|
||||
#
|
||||
# ==== Returns
|
||||
# returns the reloaded document
|
||||
def self.create(options)
|
||||
instance = new(options)
|
||||
instance.create
|
||||
instance
|
||||
end
|
||||
|
||||
# Defines an instance and save it directly to the database
|
||||
#
|
||||
# ==== Returns
|
||||
# returns the reloaded document or raises an exception
|
||||
def self.create!(options)
|
||||
instance = new(options)
|
||||
instance.create!
|
||||
instance
|
||||
end
|
||||
|
||||
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||
# decent time format by default. See Time#to_json
|
||||
def self.timestamps!
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
||||
property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
||||
|
||||
set_callback :save, :before do |object|
|
||||
write_attribute('updated_at', Time.now)
|
||||
write_attribute('created_at', Time.now) if object.new?
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
# Name a method that will be called before the document is first saved,
|
||||
# which returns a string to be used for the document's <tt>_id</tt>.
|
||||
# Because CouchDB enforces a constraint that each id must be unique,
|
||||
# this can be used to enforce eg: uniq usernames. Note that this id
|
||||
# must be globally unique across all document types which share a
|
||||
# database, so if you'd like to scope uniqueness to this class, you
|
||||
# should use the class name as part of the unique id.
|
||||
def self.unique_id method = nil, &block
|
||||
if method
|
||||
define_method :set_unique_id do
|
||||
self['_id'] ||= self.send(method)
|
||||
end
|
||||
elsif block
|
||||
define_method :set_unique_id do
|
||||
uniqid = block.call(self)
|
||||
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
||||
self['_id'] ||= uniqid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Temp solution to make the view_by methods available
|
||||
def self.method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
query = args.shift || {}
|
||||
return view(m, query, *args, &block)
|
||||
elsif m.to_s =~ /^find_(by_.+)/
|
||||
view_name = $1
|
||||
if has_view?(view_name)
|
||||
return first_from_view(view_name, *args)
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
### instance methods
|
||||
|
||||
# Gets a reference to the actual document in the DB
|
||||
# Calls up to the next document if there is one,
|
||||
# Otherwise we're at the top and we return self
|
||||
def base_doc
|
||||
return self if base_doc?
|
||||
@casted_by.base_doc
|
||||
end
|
||||
|
||||
# Checks if we're the top document
|
||||
def base_doc?
|
||||
!@casted_by
|
||||
end
|
||||
|
||||
# for compatibility with old-school frameworks
|
||||
alias :new_record? :new?
|
||||
alias :new_document? :new?
|
||||
|
||||
# Trigger the callbacks (before, after, around)
|
||||
# and create the document
|
||||
# It's important to have a create callback since you can't check if a document
|
||||
# was new after you saved it
|
||||
#
|
||||
# When creating a document, both the create and the save callbacks will be triggered.
|
||||
def create(bulk = false)
|
||||
caught = catch(:halt) do
|
||||
_run_create_callbacks do
|
||||
_run_save_callbacks do
|
||||
create_without_callbacks(bulk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# unlike save, create returns the newly created document
|
||||
def create_without_callbacks(bulk =false)
|
||||
raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
|
||||
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self, bulk)
|
||||
(result["ok"] == true) ? self : false
|
||||
end
|
||||
|
||||
# Creates the document in the db. Raises an exception
|
||||
# if the document is not created properly.
|
||||
def create!
|
||||
raise "#{self.inspect} failed to save" unless self.create
|
||||
end
|
||||
|
||||
# Trigger the callbacks (before, after, around)
|
||||
# only if the document isn't new
|
||||
def update(bulk = false)
|
||||
caught = catch(:halt) do
|
||||
if self.new?
|
||||
save(bulk)
|
||||
else
|
||||
_run_update_callbacks do
|
||||
_run_save_callbacks do
|
||||
save_without_callbacks(bulk)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Trigger the callbacks (before, after, around)
|
||||
# and save the document
|
||||
def save(bulk = false)
|
||||
caught = catch(:halt) do
|
||||
if self.new?
|
||||
_run_save_callbacks do
|
||||
save_without_callbacks(bulk)
|
||||
end
|
||||
else
|
||||
update(bulk)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Overridden to set the unique ID.
|
||||
# Returns a boolean value
|
||||
def save_without_callbacks(bulk = false)
|
||||
raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
|
||||
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self, bulk)
|
||||
result["ok"] == true
|
||||
end
|
||||
|
||||
# Saves the document to the db using save. Raises an exception
|
||||
# if the document is not saved properly.
|
||||
def save!
|
||||
raise "#{self.inspect} failed to save" unless self.save
|
||||
true
|
||||
end
|
||||
|
||||
# Deletes the document from the database. Runs the :destroy callbacks.
|
||||
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
||||
# document to be saved to a new <tt>_id</tt>.
|
||||
def destroy(bulk=false)
|
||||
caught = catch(:halt) do
|
||||
_run_destroy_callbacks do
|
||||
result = database.delete_doc(self, bulk)
|
||||
if result['ok']
|
||||
self.delete('_rev')
|
||||
self.delete('_id')
|
||||
end
|
||||
result['ok']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
mixins_dir = File.join(File.dirname(__FILE__), 'mixins')
|
||||
|
||||
require File.join(mixins_dir, 'callbacks')
|
||||
require File.join(mixins_dir, 'properties')
|
||||
require File.join(mixins_dir, 'document_queries')
|
||||
require File.join(mixins_dir, 'views')
|
||||
require File.join(mixins_dir, 'design_doc')
|
||||
require File.join(mixins_dir, 'extended_attachments')
|
||||
require File.join(mixins_dir, 'class_proxy')
|
||||
require File.join(mixins_dir, 'collection')
|
||||
require File.join(mixins_dir, 'attribute_protection')
|
||||
require File.join(mixins_dir, 'attributes')
|
||||
require File.join(mixins_dir, 'associations')
|
|
@ -1,532 +0,0 @@
|
|||
# Copyright (c) 2006-2009 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Extracted from ActiveSupport::NewCallbacks written by Yehuda Katz
|
||||
# http://github.com/rails/rails/raw/d6e4113c83a9d55be6f2af247da2cecaa855f43b/activesupport/lib/active_support/new_callbacks.rb
|
||||
# http://github.com/rails/rails/commit/1126a85aed576402d978e6f76eb393b6baaa9541
|
||||
|
||||
module CouchRest
|
||||
module Mixins
|
||||
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
||||
# before or after an alteration of the object state.
|
||||
#
|
||||
# Mixing in this module allows you to define callbacks in your class.
|
||||
#
|
||||
# Example:
|
||||
# class Storage
|
||||
# include ActiveSupport::Callbacks
|
||||
#
|
||||
# define_callbacks :save
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# save_callback :before, :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# save_callback :after do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# _run_save_callbacks do
|
||||
# puts "- save"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# config = ConfigStorage.new
|
||||
# config.save
|
||||
#
|
||||
# Output:
|
||||
# saving...
|
||||
# - save
|
||||
# saved
|
||||
#
|
||||
# Callbacks from parent classes are inherited.
|
||||
#
|
||||
# Example:
|
||||
# class Storage
|
||||
# include ActiveSupport::Callbacks
|
||||
#
|
||||
# define_callbacks :save
|
||||
#
|
||||
# save_callback :before, :prepare
|
||||
# def prepare
|
||||
# puts "preparing save"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class ConfigStorage < Storage
|
||||
# save_callback :before, :saving_message
|
||||
# def saving_message
|
||||
# puts "saving..."
|
||||
# end
|
||||
#
|
||||
# save_callback :after do |object|
|
||||
# puts "saved"
|
||||
# end
|
||||
#
|
||||
# def save
|
||||
# _run_save_callbacks do
|
||||
# puts "- save"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# config = ConfigStorage.new
|
||||
# config.save
|
||||
#
|
||||
# Output:
|
||||
# preparing save
|
||||
# saving...
|
||||
# - save
|
||||
# saved
|
||||
module Callbacks
|
||||
def self.included(klass)
|
||||
klass.extend ClassMethods
|
||||
end
|
||||
|
||||
def run_callbacks(kind, options = {}, &blk)
|
||||
send("_run_#{kind}_callbacks", &blk)
|
||||
end
|
||||
|
||||
class Callback
|
||||
@@_callback_sequence = 0
|
||||
|
||||
attr_accessor :filter, :kind, :name, :options, :per_key, :klass
|
||||
def initialize(filter, kind, options, klass)
|
||||
@kind, @klass = kind, klass
|
||||
|
||||
normalize_options!(options)
|
||||
|
||||
@per_key = options.delete(:per_key)
|
||||
@raw_filter, @options = filter, options
|
||||
@filter = _compile_filter(filter)
|
||||
@compiled_options = _compile_options(options)
|
||||
@callback_id = next_id
|
||||
|
||||
_compile_per_key_options
|
||||
end
|
||||
|
||||
def clone(klass)
|
||||
obj = super()
|
||||
obj.klass = klass
|
||||
obj.per_key = @per_key.dup
|
||||
obj.options = @options.dup
|
||||
obj.per_key[:if] = @per_key[:if].dup
|
||||
obj.per_key[:unless] = @per_key[:unless].dup
|
||||
obj.options[:if] = @options[:if].dup
|
||||
obj.options[:unless] = @options[:unless].dup
|
||||
obj
|
||||
end
|
||||
|
||||
def normalize_options!(options)
|
||||
options[:if] = Array.wrap(options[:if])
|
||||
options[:unless] = Array.wrap(options[:unless])
|
||||
|
||||
options[:per_key] ||= {}
|
||||
options[:per_key][:if] = Array.wrap(options[:per_key][:if])
|
||||
options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
|
||||
end
|
||||
|
||||
def next_id
|
||||
@@_callback_sequence += 1
|
||||
end
|
||||
|
||||
def matches?(_kind, _filter)
|
||||
@kind == _kind &&
|
||||
@filter == _filter
|
||||
end
|
||||
|
||||
def _update_filter(filter_options, new_options)
|
||||
filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
|
||||
filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
|
||||
end
|
||||
|
||||
def recompile!(_options, _per_key)
|
||||
_update_filter(self.options, _options)
|
||||
_update_filter(self.per_key, _per_key)
|
||||
|
||||
@callback_id = next_id
|
||||
@filter = _compile_filter(@raw_filter)
|
||||
@compiled_options = _compile_options(@options)
|
||||
_compile_per_key_options
|
||||
end
|
||||
|
||||
def _compile_per_key_options
|
||||
key_options = _compile_options(@per_key)
|
||||
|
||||
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def _one_time_conditions_valid_#{@callback_id}?
|
||||
true #{key_options[0]}
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
# This will supply contents for before and around filters, and no
|
||||
# contents for after filters (for the forward pass).
|
||||
def start(key = nil, options = {})
|
||||
object, terminator = (options || {}).values_at(:object, :terminator)
|
||||
|
||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||
|
||||
terminator ||= false
|
||||
|
||||
# options[0] is the compiled form of supplied conditions
|
||||
# options[1] is the "end" for the conditional
|
||||
|
||||
if @kind == :before || @kind == :around
|
||||
if @kind == :before
|
||||
# if condition # before_save :filter_name, :if => :condition
|
||||
# filter_name
|
||||
# end
|
||||
filter = <<-RUBY_EVAL
|
||||
unless halted
|
||||
result = #{@filter}
|
||||
halted = (#{terminator})
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
[@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
|
||||
else
|
||||
# Compile around filters with conditions into proxy methods
|
||||
# that contain the conditions.
|
||||
#
|
||||
# For `around_save :filter_name, :if => :condition':
|
||||
#
|
||||
# def _conditional_callback_save_17
|
||||
# if condition
|
||||
# filter_name do
|
||||
# yield self
|
||||
# end
|
||||
# else
|
||||
# yield self
|
||||
# end
|
||||
# end
|
||||
|
||||
name = "_conditional_callback_#{@kind}_#{next_id}"
|
||||
txt, line = <<-RUBY_EVAL, __LINE__ + 1
|
||||
def #{name}(halted)
|
||||
#{@compiled_options[0] || "if true"} && !halted
|
||||
#{@filter} do
|
||||
yield self
|
||||
end
|
||||
else
|
||||
yield self
|
||||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
@klass.class_eval(txt, __FILE__, line)
|
||||
"#{name}(halted) do"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This will supply contents for around and after filters, but not
|
||||
# before filters (for the backward pass).
|
||||
def end(key = nil, options = {})
|
||||
object = (options || {})[:object]
|
||||
|
||||
return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
|
||||
|
||||
if @kind == :around || @kind == :after
|
||||
# if condition # after_save :filter_name, :if => :condition
|
||||
# filter_name
|
||||
# end
|
||||
if @kind == :after
|
||||
[@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
|
||||
else
|
||||
"end"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Options support the same options as filters themselves (and support
|
||||
# symbols, string, procs, and objects), so compile a conditional
|
||||
# expression based on the options
|
||||
def _compile_options(options)
|
||||
return [] if options[:if].empty? && options[:unless].empty?
|
||||
|
||||
conditions = []
|
||||
|
||||
unless options[:if].empty?
|
||||
conditions << Array.wrap(_compile_filter(options[:if]))
|
||||
end
|
||||
|
||||
unless options[:unless].empty?
|
||||
conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
|
||||
end
|
||||
|
||||
["if #{conditions.flatten.join(" && ")}", "end"]
|
||||
end
|
||||
|
||||
# Filters support:
|
||||
# Arrays:: Used in conditions. This is used to specify
|
||||
# multiple conditions. Used internally to
|
||||
# merge conditions from skip_* filters
|
||||
# Symbols:: A method to call
|
||||
# Strings:: Some content to evaluate
|
||||
# Procs:: A proc to call with the object
|
||||
# Objects:: An object with a before_foo method on it to call
|
||||
#
|
||||
# All of these objects are compiled into methods and handled
|
||||
# the same after this point:
|
||||
# Arrays:: Merged together into a single filter
|
||||
# Symbols:: Already methods
|
||||
# Strings:: class_eval'ed into methods
|
||||
# Procs:: define_method'ed into methods
|
||||
# Objects::
|
||||
# a method is created that calls the before_foo method
|
||||
# on the object.
|
||||
def _compile_filter(filter)
|
||||
method_name = "_callback_#{@kind}_#{next_id}"
|
||||
case filter
|
||||
when Array
|
||||
filter.map {|f| _compile_filter(f)}
|
||||
when Symbol
|
||||
filter
|
||||
when String
|
||||
"(#{filter})"
|
||||
when Proc
|
||||
@klass.send(:define_method, method_name, &filter)
|
||||
return method_name if filter.arity == 0
|
||||
|
||||
method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ")
|
||||
else
|
||||
@klass.send(:define_method, "#{method_name}_object") { filter }
|
||||
|
||||
_normalize_legacy_filter(kind, filter)
|
||||
|
||||
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def #{method_name}(&blk)
|
||||
#{method_name}_object.send(:#{kind}, self, &blk)
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
method_name
|
||||
end
|
||||
end
|
||||
|
||||
def _normalize_legacy_filter(kind, filter)
|
||||
if !filter.respond_to?(kind) && filter.respond_to?(:filter)
|
||||
filter.class_eval(
|
||||
"def #{kind}(context, &block) filter(context, &block) end",
|
||||
__FILE__, __LINE__ - 1)
|
||||
elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around
|
||||
def filter.around(context)
|
||||
should_continue = before(context)
|
||||
yield if should_continue
|
||||
after(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# An Array with a compile method
|
||||
class CallbackChain < Array
|
||||
def initialize(symbol)
|
||||
@symbol = symbol
|
||||
end
|
||||
|
||||
def compile(key = nil, options = {})
|
||||
method = []
|
||||
method << "halted = false"
|
||||
each do |callback|
|
||||
method << callback.start(key, options)
|
||||
end
|
||||
method << "yield self if block_given? && !halted"
|
||||
reverse_each do |callback|
|
||||
method << callback.end(key, options)
|
||||
end
|
||||
method.compact.join("\n")
|
||||
end
|
||||
|
||||
def clone(klass)
|
||||
chain = CallbackChain.new(@symbol)
|
||||
chain.push(*map {|c| c.clone(klass)})
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
#CHAINS = {:before => :before, :around => :before, :after => :after}
|
||||
|
||||
# Make the _run_save_callbacks method. The generated method takes
|
||||
# a block that it'll yield to. It'll call the before and around filters
|
||||
# in order, yield the block, and then run the after filters.
|
||||
#
|
||||
# _run_save_callbacks do
|
||||
# save
|
||||
# end
|
||||
#
|
||||
# The _run_save_callbacks method can optionally take a key, which
|
||||
# will be used to compile an optimized callback method for each
|
||||
# key. See #define_callbacks for more information.
|
||||
def _define_runner(symbol)
|
||||
body = send("_#{symbol}_callback").
|
||||
compile(nil, :terminator => send("_#{symbol}_terminator"))
|
||||
|
||||
body, line = <<-RUBY_EVAL, __LINE__ + 1
|
||||
def _run_#{symbol}_callbacks(key = nil, &blk)
|
||||
if key
|
||||
name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
|
||||
|
||||
unless respond_to?(name)
|
||||
self.class._create_keyed_callback(name, :#{symbol}, self, &blk)
|
||||
end
|
||||
|
||||
send(name, &blk)
|
||||
else
|
||||
#{body}
|
||||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
|
||||
undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
|
||||
class_eval body, __FILE__, line
|
||||
end
|
||||
|
||||
# This is called the first time a callback is called with a particular
|
||||
# key. It creates a new callback method for the key, calculating
|
||||
# which callbacks can be omitted because of per_key conditions.
|
||||
def _create_keyed_callback(name, kind, obj, &blk)
|
||||
@_keyed_callbacks ||= {}
|
||||
@_keyed_callbacks[name] ||= begin
|
||||
str = send("_#{kind}_callback").
|
||||
compile(name, :object => obj, :terminator => send("_#{kind}_terminator"))
|
||||
|
||||
class_eval "def #{name}() #{str} end", __FILE__, __LINE__
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Define callbacks.
|
||||
#
|
||||
# Creates a <name>_callback method that you can use to add callbacks.
|
||||
#
|
||||
# Syntax:
|
||||
# save_callback :before, :before_meth
|
||||
# save_callback :after, :after_meth, :if => :condition
|
||||
# save_callback :around {|r| stuff; yield; stuff }
|
||||
#
|
||||
# The <name>_callback method also updates the _run_<name>_callbacks
|
||||
# method, which is the public API to run the callbacks.
|
||||
#
|
||||
# Also creates a skip_<name>_callback method that you can use to skip
|
||||
# callbacks.
|
||||
#
|
||||
# When creating or skipping callbacks, you can specify conditions that
|
||||
# are always the same for a given key. For instance, in ActionPack,
|
||||
# we convert :only and :except conditions into per-key conditions.
|
||||
#
|
||||
# before_filter :authenticate, :except => "index"
|
||||
# becomes
|
||||
# dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
|
||||
#
|
||||
# Per-Key conditions are evaluated only once per use of a given key.
|
||||
# In the case of the above example, you would do:
|
||||
#
|
||||
# run_dispatch_callbacks(action_name) { ... dispatch stuff ... }
|
||||
#
|
||||
# In that case, each action_name would get its own compiled callback
|
||||
# method that took into consideration the per_key conditions. This
|
||||
# is a speed improvement for ActionPack.
|
||||
def _update_callbacks(name, filters = CallbackChain.new(name), block = nil)
|
||||
type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
|
||||
options = filters.last.is_a?(Hash) ? filters.pop : {}
|
||||
filters.unshift(block) if block
|
||||
|
||||
callbacks = send("_#{name}_callback")
|
||||
yield callbacks, type, filters, options if block_given?
|
||||
|
||||
_define_runner(name)
|
||||
end
|
||||
|
||||
alias_method :_reset_callbacks, :_update_callbacks
|
||||
|
||||
def set_callback(name, *filters, &block)
|
||||
_update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
filters.map! do |filter|
|
||||
# overrides parent class
|
||||
callbacks.delete_if {|c| c.matches?(type, filter) }
|
||||
Callback.new(filter, type, options.dup, self)
|
||||
end
|
||||
|
||||
options[:prepend] ? callbacks.unshift(*filters) : callbacks.push(*filters)
|
||||
end
|
||||
end
|
||||
|
||||
def skip_callback(name, *filters, &block)
|
||||
_update_callbacks(name, filters, block) do |callbacks, type, filters, options|
|
||||
filters.each do |filter|
|
||||
callbacks = send("_#{name}_callback=", callbacks.clone(self))
|
||||
|
||||
filter = callbacks.find {|c| c.matches?(type, filter) }
|
||||
|
||||
if filter && options.any?
|
||||
filter.recompile!(options, options[:per_key] || {})
|
||||
else
|
||||
callbacks.delete(filter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def define_callbacks(*symbols)
|
||||
terminator = symbols.pop if symbols.last.is_a?(String)
|
||||
symbols.each do |symbol|
|
||||
extlib_inheritable_accessor("_#{symbol}_terminator") { terminator }
|
||||
|
||||
extlib_inheritable_accessor("_#{symbol}_callback") do
|
||||
CallbackChain.new(symbol)
|
||||
end
|
||||
|
||||
_define_runner(symbol)
|
||||
|
||||
# Define more convenient callback methods
|
||||
# set_callback(:save, :before) becomes before_save
|
||||
[:before, :after, :around].each do |filter|
|
||||
self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def self.#{filter}_#{symbol}(*symbols, &blk)
|
||||
_alias_callbacks(symbols, blk) do |callback, options|
|
||||
set_callback(:#{symbol}, :#{filter}, callback, options)
|
||||
end
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def _alias_callbacks(callbacks, block)
|
||||
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
|
||||
callbacks.push(block) if block
|
||||
callbacks.each do |callback|
|
||||
yield callback, options
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/couchrest/model.rb
Normal file
10
lib/couchrest/model.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
module CouchRest
|
||||
|
||||
module Model
|
||||
|
||||
VERSION = "1.0.0.beta7"
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,7 +1,5 @@
|
|||
module CouchRest
|
||||
|
||||
|
||||
module Mixins
|
||||
module Model
|
||||
module Associations
|
||||
|
||||
# Basic support for relationships between ExtendedDocuments
|
||||
|
@ -47,12 +45,12 @@ module CouchRest
|
|||
# collection_of :groups
|
||||
#
|
||||
# creates a pseudo property called "groups" which allows access
|
||||
# to a CollectionProxy object. Adding, replacing or removing entries in this
|
||||
# to a CollectionOfProxy object. Adding, replacing or removing entries in this
|
||||
# proxy will cause the matching property array, in this case "group_ids", to
|
||||
# be kept in sync.
|
||||
#
|
||||
# Any manual changes made to the collection ids property (group_ids), unless replaced, will require
|
||||
# a reload of the CollectionProxy for the two sets of data to be in sync:
|
||||
# a reload of the CollectionOfProxy for the two sets of data to be in sync:
|
||||
#
|
||||
# group_ids = ['123']
|
||||
# groups == [Group.get('123')]
|
||||
|
@ -63,7 +61,7 @@ module CouchRest
|
|||
# Of course, saving the parent record will store the collection ids as they are
|
||||
# found.
|
||||
#
|
||||
# The CollectionProxy supports the following array functions, anything else will cause
|
||||
# The CollectionOfProxy supports the following array functions, anything else will cause
|
||||
# a mismatch between the collection objects and collection ids:
|
||||
#
|
||||
# groups << obj
|
||||
|
@ -128,7 +126,7 @@ module CouchRest
|
|||
### collection_of support methods
|
||||
|
||||
def create_collection_of_property_setter(attrib, property, options)
|
||||
# ensure CollectionProxy is nil, ready to be reloaded on request
|
||||
# ensure CollectionOfProxy is nil, ready to be reloaded on request
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{options[:foreign_key]}=(value)
|
||||
@#{attrib} = nil
|
||||
|
@ -143,7 +141,7 @@ module CouchRest
|
|||
def #{attrib}(reload = false)
|
||||
return @#{attrib} unless @#{attrib}.nil? or reload
|
||||
ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
|
||||
@#{attrib} = ::CouchRest::CollectionProxy.new(ary, self, '#{options[:foreign_key]}')
|
||||
@#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -151,7 +149,7 @@ module CouchRest
|
|||
def create_collection_of_setter(attrib, property, options)
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}=(value)
|
||||
@#{attrib} = ::CouchRest::CollectionProxy.new(value, self, '#{options[:foreign_key]}')
|
||||
@#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -163,7 +161,7 @@ module CouchRest
|
|||
|
||||
# Special proxy for a collection of items so that adding and removing
|
||||
# to the list automatically updates the associated property.
|
||||
class CollectionProxy < Array
|
||||
class CollectionOfProxy < Array
|
||||
attr_accessor :property
|
||||
attr_accessor :casted_by
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module AttributeProtection
|
||||
# Attribute protection from mass assignment to CouchRest properties
|
||||
#
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Attributes
|
||||
|
||||
## Support for handling attributes
|
93
lib/couchrest/model/base.rb
Normal file
93
lib/couchrest/model/base.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
class Base < Document
|
||||
|
||||
extend ActiveModel::Naming
|
||||
|
||||
include CouchRest::Model::Persistence
|
||||
include CouchRest::Model::Callbacks
|
||||
include CouchRest::Model::DocumentQueries
|
||||
include CouchRest::Model::Views
|
||||
include CouchRest::Model::DesignDoc
|
||||
include CouchRest::Model::ExtendedAttachments
|
||||
include CouchRest::Model::ClassProxy
|
||||
include CouchRest::Model::Collection
|
||||
include CouchRest::Model::AttributeProtection
|
||||
include CouchRest::Model::Attributes
|
||||
include CouchRest::Model::Associations
|
||||
include CouchRest::Model::Validations
|
||||
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
end
|
||||
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.send(:include, CouchRest::Model::Properties)
|
||||
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def self.inherited(subklass)
|
||||
super
|
||||
subklass.properties = self.properties.dup
|
||||
# This is nasty:
|
||||
subklass._validators = self._validators.dup
|
||||
end
|
||||
EOS
|
||||
subclasses << subklass
|
||||
end
|
||||
|
||||
# Accessors
|
||||
attr_accessor :casted_by
|
||||
|
||||
|
||||
# Instantiate a new ExtendedDocument by preparing all properties
|
||||
# using the provided document hash.
|
||||
#
|
||||
# Options supported:
|
||||
#
|
||||
# * :directly_set_attributes: true when data comes directly from database
|
||||
#
|
||||
def initialize(doc = {}, options = {})
|
||||
prepare_all_attributes(doc, options)
|
||||
super(doc)
|
||||
unless self['_id'] && self['_rev']
|
||||
self['couchrest-type'] = self.class.to_s
|
||||
end
|
||||
after_initialize if respond_to?(:after_initialize)
|
||||
end
|
||||
|
||||
|
||||
# Temp solution to make the view_by methods available
|
||||
def self.method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
query = args.shift || {}
|
||||
return view(m, query, *args, &block)
|
||||
elsif m.to_s =~ /^find_(by_.+)/
|
||||
view_name = $1
|
||||
if has_view?(view_name)
|
||||
return first_from_view(view_name, *args)
|
||||
end
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
### instance methods
|
||||
|
||||
# Gets a reference to the actual document in the DB
|
||||
# Calls up to the next document if there is one,
|
||||
# Otherwise we're at the top and we return self
|
||||
def base_doc
|
||||
return self if base_doc?
|
||||
@casted_by.base_doc
|
||||
end
|
||||
|
||||
# Checks if we're the top document
|
||||
def base_doc?
|
||||
!@casted_by
|
||||
end
|
||||
|
||||
# for compatibility with old-school frameworks
|
||||
alias :new_record? :new?
|
||||
alias :new_document? :new?
|
||||
end
|
||||
end
|
||||
end
|
27
lib/couchrest/model/callbacks.rb
Normal file
27
lib/couchrest/model/callbacks.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# encoding: utf-8
|
||||
|
||||
module CouchRest #:nodoc:
|
||||
module Model #:nodoc:
|
||||
|
||||
module Callbacks
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
extend ActiveModel::Callbacks
|
||||
|
||||
define_model_callbacks \
|
||||
:create,
|
||||
:destroy,
|
||||
:save,
|
||||
:update,
|
||||
:validate
|
||||
|
||||
end
|
||||
|
||||
def valid?(*) #nodoc
|
||||
_run_validation_callbacks { super }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@
|
|||
# elements of the array.
|
||||
#
|
||||
|
||||
module CouchRest
|
||||
module CouchRest::Model
|
||||
class CastedArray < Array
|
||||
attr_accessor :casted_by
|
||||
attr_accessor :property
|
|
@ -1,13 +1,16 @@
|
|||
module CouchRest
|
||||
module CouchRest::Model
|
||||
module CastedModel
|
||||
|
||||
def self.included(base)
|
||||
base.send(:include, ::CouchRest::Mixins::AttributeProtection)
|
||||
base.send(:include, ::CouchRest::Mixins::Attributes)
|
||||
base.send(:include, ::CouchRest::Mixins::Callbacks)
|
||||
base.send(:include, ::CouchRest::Mixins::Properties)
|
||||
base.send(:include, ::CouchRest::Mixins::Associations)
|
||||
base.send(:attr_accessor, :casted_by)
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include CouchRest::Model::AttributeProtection
|
||||
include CouchRest::Model::Attributes
|
||||
include CouchRest::Model::Callbacks
|
||||
include CouchRest::Model::Properties
|
||||
include CouchRest::Model::Associations
|
||||
include CouchRest::Model::Validations
|
||||
attr_accessor :casted_by
|
||||
end
|
||||
|
||||
def initialize(keys = {})
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module ClassProxy
|
||||
|
||||
def self.included(base)
|
||||
|
@ -36,7 +36,7 @@ module CouchRest
|
|||
@database = database
|
||||
end
|
||||
|
||||
# ExtendedDocument
|
||||
# Base
|
||||
|
||||
def new(*args)
|
||||
doc = @klass.new(*args)
|
||||
|
@ -57,7 +57,7 @@ module CouchRest
|
|||
super
|
||||
end
|
||||
|
||||
# Mixins::DocumentQueries
|
||||
# DocumentQueries
|
||||
|
||||
def all(opts = {}, &block)
|
||||
docs = @klass.all({:database => @database}.merge(opts), &block)
|
||||
|
@ -82,7 +82,7 @@ module CouchRest
|
|||
end
|
||||
alias :find :get
|
||||
|
||||
# Mixins::Views
|
||||
# Views
|
||||
|
||||
def has_view?(view)
|
||||
@klass.has_view?(view)
|
||||
|
@ -102,7 +102,7 @@ module CouchRest
|
|||
doc
|
||||
end
|
||||
|
||||
# Mixins::DesignDoc
|
||||
# DesignDoc
|
||||
|
||||
def design_doc
|
||||
@klass.design_doc
|
||||
|
@ -116,16 +116,6 @@ module CouchRest
|
|||
@klass.save_design_doc(@database)
|
||||
end
|
||||
|
||||
# DEPRICATED
|
||||
def all_design_doc_versions
|
||||
@klass.all_design_doc_versions(@database)
|
||||
end
|
||||
|
||||
def stored_design_doc
|
||||
@klass.stored_design_doc(@database)
|
||||
end
|
||||
alias :model_design_doc :stored_design_doc
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Collection
|
||||
|
||||
def self.included(base)
|
|
@ -1,7 +1,6 @@
|
|||
require 'digest/md5'
|
||||
|
||||
# encoding: utf-8
|
||||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module DesignDoc
|
||||
|
||||
def self.included(base)
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module DocumentQueries
|
||||
|
||||
def self.included(base)
|
23
lib/couchrest/model/errors.rb
Normal file
23
lib/couchrest/model/errors.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# encoding: utf-8
|
||||
module CouchRest
|
||||
module Model
|
||||
module Errors
|
||||
|
||||
class CouchRestModelError < StandardError; end
|
||||
|
||||
# Raised when a persisence method ending in ! fails validation. The message
|
||||
# will contain the full error messages from the +Document+ in question.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# <tt>Validations.new(person.errors)</tt>
|
||||
class Validations < CouchRestModelError
|
||||
attr_reader :document
|
||||
def initialize(document)
|
||||
@document = document
|
||||
super("Validation Failed: #{@document.errors.full_messages.join(", ")}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module ExtendedAttachments
|
||||
|
||||
# Add a file attachment to the current document. Expects
|
141
lib/couchrest/model/persistence.rb
Normal file
141
lib/couchrest/model/persistence.rb
Normal file
|
@ -0,0 +1,141 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module Persistence
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Create the document. Validation is enabled by default and will return
|
||||
# false if the document is not valid. If all goes well, the document will
|
||||
# be returned.
|
||||
def create(options = {})
|
||||
return false unless perform_validations(options)
|
||||
_run_create_callbacks do
|
||||
_run_save_callbacks do
|
||||
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self)
|
||||
(result["ok"] == true) ? self : false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates the document in the db. Raises an exception
|
||||
# if the document is not created properly.
|
||||
def create!
|
||||
self.class.fail_validate!(self) unless self.create
|
||||
end
|
||||
|
||||
# Trigger the callbacks (before, after, around)
|
||||
# only if the document isn't new
|
||||
def update(options = {})
|
||||
raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
|
||||
return false unless perform_validations(options)
|
||||
_run_update_callbacks do
|
||||
_run_save_callbacks do
|
||||
result = database.save_doc(self)
|
||||
result["ok"] == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Trigger the callbacks (before, after, around) and save the document
|
||||
def save(options = {})
|
||||
self.new? ? create(options) : update(options)
|
||||
end
|
||||
|
||||
# Saves the document to the db using save. Raises an exception
|
||||
# if the document is not saved properly.
|
||||
def save!
|
||||
self.class.fail_validate!(self) unless self.save
|
||||
true
|
||||
end
|
||||
|
||||
# Deletes the document from the database. Runs the :destroy callbacks.
|
||||
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
||||
# document to be saved to a new <tt>_id</tt> if required.
|
||||
def destroy
|
||||
_run_destroy_callbacks do
|
||||
result = database.delete_doc(self)
|
||||
if result['ok']
|
||||
self.delete('_rev')
|
||||
self.delete('_id')
|
||||
end
|
||||
result['ok']
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def perform_validations(options = {})
|
||||
perform_validation = case options
|
||||
when Hash
|
||||
options[:validate] != false
|
||||
else
|
||||
options
|
||||
end
|
||||
perform_validation ? valid? : true
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Creates a new instance, bypassing attribute protection
|
||||
#
|
||||
#
|
||||
# ==== Returns
|
||||
# a document instance
|
||||
def create_from_database(doc = {})
|
||||
base = (doc['couchrest-type'].blank? || doc['couchrest-type'] == self.to_s) ? self : doc['couchrest-type'].constantize
|
||||
base.new(doc, :directly_set_attributes => true)
|
||||
end
|
||||
|
||||
# Defines an instance and save it directly to the database
|
||||
#
|
||||
# ==== Returns
|
||||
# returns the reloaded document
|
||||
def create(attributes = {})
|
||||
instance = new(attributes)
|
||||
instance.create
|
||||
instance
|
||||
end
|
||||
|
||||
# Defines an instance and save it directly to the database
|
||||
#
|
||||
# ==== Returns
|
||||
# returns the reloaded document or raises an exception
|
||||
def create!(attributes = {})
|
||||
instance = new(attributes)
|
||||
instance.create!
|
||||
instance
|
||||
end
|
||||
|
||||
# Name a method that will be called before the document is first saved,
|
||||
# which returns a string to be used for the document's <tt>_id</tt>.
|
||||
#
|
||||
# Because CouchDB enforces a constraint that each id must be unique,
|
||||
# this can be used to enforce eg: uniq usernames. Note that this id
|
||||
# must be globally unique across all document types which share a
|
||||
# database, so if you'd like to scope uniqueness to this class, you
|
||||
# should use the class name as part of the unique id.
|
||||
def unique_id method = nil, &block
|
||||
if method
|
||||
define_method :set_unique_id do
|
||||
self['_id'] ||= self.send(method)
|
||||
end
|
||||
elsif block
|
||||
define_method :set_unique_id do
|
||||
uniqid = block.call(self)
|
||||
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
||||
self['_id'] ||= uniqid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Raise an error if validation failed.
|
||||
def fail_validate!(document)
|
||||
raise Errors::Validations.new(document)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +1,6 @@
|
|||
require 'time'
|
||||
require File.join(File.dirname(__FILE__), '..', 'property')
|
||||
require File.join(File.dirname(__FILE__), '..', 'casted_array')
|
||||
|
||||
# encoding: utf-8
|
||||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Properties
|
||||
|
||||
class IncludeError < StandardError; end
|
||||
|
@ -30,7 +27,7 @@ module CouchRest
|
|||
end
|
||||
|
||||
def write_attribute(property, value)
|
||||
prop = property.is_a?(::CouchRest::Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
||||
prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
||||
raise "Missing property definition for #{property.to_s}" unless prop
|
||||
self[prop.to_s] = prop.cast(self, value)
|
||||
end
|
||||
|
@ -59,6 +56,21 @@ module CouchRest
|
|||
define_property(name, opts, &block)
|
||||
end
|
||||
end
|
||||
|
||||
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
||||
# on the document whenever saving occurs. CouchRest uses a pretty
|
||||
# decent time format by default. See Time#to_json
|
||||
def timestamps!
|
||||
class_eval <<-EOS, __FILE__, __LINE__
|
||||
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
||||
property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
||||
|
||||
set_callback :save, :before do |object|
|
||||
write_attribute('updated_at', Time.now)
|
||||
write_attribute('created_at', Time.now) if object.new?
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
|
@ -74,9 +86,12 @@ module CouchRest
|
|||
type.class_eval { yield type }
|
||||
type = [type] # inject as an array
|
||||
end
|
||||
property = CouchRest::Property.new(name, type, options)
|
||||
property = Property.new(name, type, options)
|
||||
create_property_getter(property)
|
||||
create_property_setter(property) unless property.read_only == true
|
||||
if property.type_class.respond_to?(:validates_casted_model)
|
||||
validates_casted_model property.name
|
||||
end
|
||||
properties << property
|
||||
property
|
||||
end
|
|
@ -1,14 +1,10 @@
|
|||
|
||||
require File.join(File.dirname(__FILE__), 'mixins', 'typecast')
|
||||
|
||||
module CouchRest
|
||||
|
||||
# Basic attribute support for adding getter/setter + validation
|
||||
# encoding: utf-8
|
||||
module CouchRest::Model
|
||||
class Property
|
||||
|
||||
include ::CouchRest::Mixins::Typecast
|
||||
include ::CouchRest::Model::Typecast
|
||||
|
||||
attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
|
||||
attr_reader :name, :type, :type_class, :read_only, :alias, :default, :casted, :init_method, :options
|
||||
|
||||
# Attribute to define.
|
||||
# All Properties are assumed casted unless the type is nil.
|
||||
|
@ -42,7 +38,7 @@ module CouchRest
|
|||
end
|
||||
arr = value.collect { |data| cast_value(parent, data) }
|
||||
# allow casted_by calls to be passed up chain by wrapping in CastedArray
|
||||
value = type_class != String ? ::CouchRest::CastedArray.new(arr, self) : arr
|
||||
value = type_class != String ? CastedArray.new(arr, self) : arr
|
||||
value.casted_by = parent if value.respond_to?(:casted_by)
|
||||
elsif !value.nil?
|
||||
value = cast_value(parent, value)
|
||||
|
@ -66,17 +62,6 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
|
||||
# Always provide the basic type as a class. If the type
|
||||
# is an array, the class will be extracted.
|
||||
def type_class
|
||||
return String unless casted # This is rubbish, to handle validations
|
||||
return @type_class unless @type_class.nil?
|
||||
base = @type.is_a?(Array) ? @type.first : @type
|
||||
base = String if base.nil?
|
||||
base = TrueClass if base.is_a?(String) && base.downcase == 'boolean'
|
||||
@type_class = base.is_a?(Class) ? base : base.constantize
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def associate_casted_value_to_parent(parent, value)
|
||||
|
@ -88,7 +73,12 @@ module CouchRest
|
|||
if type.nil?
|
||||
@casted = false
|
||||
@type = nil
|
||||
@type_class = nil
|
||||
else
|
||||
base = type.is_a?(Array) ? type.first : type
|
||||
base = Object if base.nil?
|
||||
raise "Defining a property type as a #{type.class.name.humanize} is not supported in CouchRest Model!" if base.class != Class
|
||||
@type_class = base
|
||||
@type = type
|
||||
end
|
||||
end
|
|
@ -5,13 +5,13 @@ module CouchRest
|
|||
|
||||
alias :delete_old! :delete!
|
||||
def delete!
|
||||
clear_extended_doc_fresh_cache
|
||||
clear_model_fresh_cache
|
||||
delete_old!
|
||||
end
|
||||
|
||||
# If the database is deleted, ensure that the design docs will be refreshed.
|
||||
def clear_extended_doc_fresh_cache
|
||||
::CouchRest::ExtendedDocument.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
|
||||
def clear_model_fresh_cache
|
||||
::CouchRest::Model::Base.subclasses.each{|klass| klass.req_design_doc_refresh if klass.respond_to?(:req_design_doc_refresh)}
|
||||
end
|
||||
|
||||
end
|
|
@ -21,7 +21,7 @@ CouchRest::Document.class_eval do
|
|||
alias_method :kind_of?, :is_a?
|
||||
end
|
||||
|
||||
CouchRest::CastedModel.class_eval do
|
||||
CouchRest::Model::CastedModel.class_eval do
|
||||
# The to_param method is needed for rails to generate resourceful routes.
|
||||
# In your controller, remember that it's actually the id of the document.
|
||||
def id
|
||||
|
@ -30,13 +30,3 @@ CouchRest::CastedModel.class_eval do
|
|||
end
|
||||
alias_method :to_param, :id
|
||||
end
|
||||
|
||||
require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
|
||||
|
||||
CouchRest::Validation::ValidationErrors.class_eval do
|
||||
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
|
||||
# This method is called by error_messages_for
|
||||
def count
|
||||
errors.values.inject(0) { |error_count, errors_for_attribute| error_count + errors_for_attribute.size }
|
||||
end
|
||||
end
|
|
@ -1,7 +1,3 @@
|
|||
require 'time'
|
||||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
class Time
|
||||
# returns a local time value much faster than Time.parse
|
||||
def self.mktime_with_offset(string)
|
||||
|
@ -22,7 +18,7 @@ class Time
|
|||
end
|
||||
|
||||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Typecast
|
||||
|
||||
def typecast_value(value, property) # klass, init_method)
|
32
lib/couchrest/model/validations.rb
Normal file
32
lib/couchrest/model/validations.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require "couchrest/model/validations/casted_model"
|
||||
|
||||
module CouchRest
|
||||
module Model
|
||||
|
||||
# Validations may be applied to both Model::Base and Model::CastedModel
|
||||
module Validations
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
include ActiveModel::Validations
|
||||
end
|
||||
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Validates the associated casted model. This method should not be
|
||||
# used within your code as it is automatically included when a CastedModel
|
||||
# is used inside the model.
|
||||
#
|
||||
def validates_casted_model(*args)
|
||||
validates_with(CastedModelValidator, _merge_attributes(args))
|
||||
end
|
||||
|
||||
# TODO: Here will lie validates_uniqueness_of
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
14
lib/couchrest/model/validations/casted_model.rb
Normal file
14
lib/couchrest/model/validations/casted_model.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module Validations
|
||||
class CastedModelValidator < ActiveModel::EachValidator
|
||||
|
||||
def validate_each(document, attribute, value)
|
||||
values = value.is_a?(Array) ? value : [value]
|
||||
return if values.collect {|doc| doc.nil? || doc.valid? }.all?
|
||||
document.errors.add(attribute, :invalid, :default => options[:message], :value => value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +1,7 @@
|
|||
module CouchRest
|
||||
module Mixins
|
||||
module Model
|
||||
module Views
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Define a CouchDB view. The name of the view will be the concatenation
|
|
@ -1,244 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
class Object
|
||||
def validatable?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
require 'pathname'
|
||||
|
||||
dir = File.join(Pathname(__FILE__).dirname.expand_path, 'validation')
|
||||
|
||||
require File.join(dir, 'validation_errors')
|
||||
require File.join(dir, 'contextual_validators')
|
||||
require File.join(dir, 'auto_validate')
|
||||
|
||||
require File.join(dir, 'validators', 'generic_validator')
|
||||
require File.join(dir, 'validators', 'required_field_validator')
|
||||
require File.join(dir, 'validators', 'absent_field_validator')
|
||||
require File.join(dir, 'validators', 'format_validator')
|
||||
require File.join(dir, 'validators', 'length_validator')
|
||||
require File.join(dir, 'validators', 'numeric_validator')
|
||||
require File.join(dir, 'validators', 'method_validator')
|
||||
require File.join(dir, 'validators', 'confirmation_validator')
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
def self.included(base)
|
||||
base.extlib_inheritable_accessor(:auto_validation)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
# Callbacks
|
||||
define_callbacks :validate
|
||||
|
||||
# Turn off auto validation by default
|
||||
self.auto_validation ||= false
|
||||
|
||||
# Force the auto validation for the class properties
|
||||
# This feature is still not fully ported over,
|
||||
# test are lacking, so please use with caution
|
||||
def self.auto_validate!
|
||||
self.auto_validation = true
|
||||
end
|
||||
|
||||
# share the validations with subclasses
|
||||
def self.inherited(subklass)
|
||||
self.validators.contexts.each do |k, v|
|
||||
subklass.validators.contexts[k] = v.dup
|
||||
end
|
||||
super
|
||||
end
|
||||
EOS
|
||||
|
||||
base.extend(ClassMethods)
|
||||
base.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
define_callbacks :validate
|
||||
if method_defined?(:_run_save_callbacks)
|
||||
set_callback :save, :before, :check_validations
|
||||
end
|
||||
EOS
|
||||
base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
||||
def self.define_property(name, options={}, &block)
|
||||
property = super
|
||||
auto_generate_validations(property) unless property.nil?
|
||||
end
|
||||
RUBY_EVAL
|
||||
end
|
||||
|
||||
# Ensures the object is valid for the context provided, and otherwise
|
||||
# throws :halt and returns false.
|
||||
#
|
||||
def check_validations(context = :default)
|
||||
throw(:halt, false) unless context.nil? || valid?(context)
|
||||
end
|
||||
|
||||
# Return the ValidationErrors
|
||||
#
|
||||
def errors
|
||||
@errors ||= ValidationErrors.new
|
||||
end
|
||||
|
||||
# Mark this resource as validatable. When we validate associations of a
|
||||
# resource we can check if they respond to validatable? before trying to
|
||||
# recursivly validate them
|
||||
#
|
||||
def validatable?
|
||||
true
|
||||
end
|
||||
|
||||
# Alias for valid?(:default)
|
||||
#
|
||||
def valid_for_default?
|
||||
valid?(:default)
|
||||
end
|
||||
|
||||
# Check if a resource is valid in a given context
|
||||
#
|
||||
def valid?(context = :default)
|
||||
recursive_valid?(self, context, true)
|
||||
end
|
||||
|
||||
# checking on casted objects
|
||||
def validate_casted_arrays
|
||||
result = true
|
||||
array_casted_properties = self.class.properties.select { |property| property.casted && property.type.instance_of?(Array) }
|
||||
array_casted_properties.each do |property|
|
||||
casted_values = self.send(property.name)
|
||||
next unless casted_values.is_a?(Array) && casted_values.first.respond_to?(:valid?)
|
||||
casted_values.each do |value|
|
||||
result = (result && value.valid?) if value.respond_to?(:valid?)
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Do recursive validity checking
|
||||
#
|
||||
def recursive_valid?(target, context, state)
|
||||
valid = state
|
||||
target.each do |key, prop|
|
||||
if prop.is_a?(Array)
|
||||
prop.each do |item|
|
||||
if item.validatable?
|
||||
valid = recursive_valid?(item, context, valid) && valid
|
||||
end
|
||||
end
|
||||
elsif prop.validatable?
|
||||
valid = recursive_valid?(prop, context, valid) && valid
|
||||
end
|
||||
end
|
||||
target._run_validate_callbacks do
|
||||
target.class.validators.execute(context, target) && valid
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def validation_property_value(name)
|
||||
self.respond_to?(name, true) ? self.send(name) : nil
|
||||
end
|
||||
|
||||
# Get the corresponding Object property, if it exists.
|
||||
def validation_property(field_name)
|
||||
properties.find{|p| p.name == field_name}
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
include CouchRest::Validation::ValidatesPresent
|
||||
include CouchRest::Validation::ValidatesAbsent
|
||||
include CouchRest::Validation::ValidatesIsConfirmed
|
||||
# include CouchRest::Validation::ValidatesIsPrimitive
|
||||
# include CouchRest::Validation::ValidatesIsAccepted
|
||||
include CouchRest::Validation::ValidatesFormat
|
||||
include CouchRest::Validation::ValidatesLength
|
||||
# include CouchRest::Validation::ValidatesWithin
|
||||
include CouchRest::Validation::ValidatesIsNumber
|
||||
include CouchRest::Validation::ValidatesWithMethod
|
||||
# include CouchRest::Validation::ValidatesWithBlock
|
||||
# include CouchRest::Validation::ValidatesIsUnique
|
||||
include CouchRest::Validation::AutoValidate
|
||||
|
||||
# Return the set of contextual validators or create a new one
|
||||
#
|
||||
def validators
|
||||
@validations ||= ContextualValidators.new
|
||||
end
|
||||
|
||||
# Clean up the argument list and return a opts hash, including the
|
||||
# merging of any default opts. Set the context to default if none is
|
||||
# provided. Also allow :context to be aliased to :on, :when & group
|
||||
#
|
||||
def opts_from_validator_args(args, defaults = nil)
|
||||
opts = args.last.kind_of?(Hash) ? args.pop : {}
|
||||
context = :default
|
||||
context = opts[:context] if opts.has_key?(:context)
|
||||
context = opts.delete(:on) if opts.has_key?(:on)
|
||||
context = opts.delete(:when) if opts.has_key?(:when)
|
||||
context = opts.delete(:group) if opts.has_key?(:group)
|
||||
opts[:context] = context
|
||||
opts.merge!(defaults) unless defaults.nil?
|
||||
opts
|
||||
end
|
||||
|
||||
# Given a new context create an instance method of
|
||||
# valid_for_<context>? which simply calls valid?(context)
|
||||
# if it does not already exist
|
||||
#
|
||||
def create_context_instance_methods(context)
|
||||
name = "valid_for_#{context.to_s}?" # valid_for_signup?
|
||||
if !self.instance_methods.include?(name)
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{name} # def valid_for_signup?
|
||||
valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
|
||||
end # end
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new validator of the given klazz and push it onto the
|
||||
# requested context for each of the attributes in the fields list
|
||||
#
|
||||
def add_validator_to_context(opts, fields, klazz)
|
||||
fields.each do |field|
|
||||
validator = klazz.new(field.to_sym, opts)
|
||||
if opts[:context].is_a?(Symbol)
|
||||
unless validators.context(opts[:context]).include?(validator)
|
||||
validators.context(opts[:context]) << validator
|
||||
create_context_instance_methods(opts[:context])
|
||||
end
|
||||
elsif opts[:context].is_a?(Array)
|
||||
opts[:context].each do |c|
|
||||
unless validators.context(c).include?(validator)
|
||||
validators.context(c) << validator
|
||||
create_context_instance_methods(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # module ClassMethods
|
||||
end # module Validation
|
||||
|
||||
end # module CouchRest
|
|
@ -1,156 +0,0 @@
|
|||
# Ported from dm-migrations
|
||||
|
||||
module CouchRest
|
||||
|
||||
class Property
|
||||
# flag letting us know if we already checked the autovalidation settings
|
||||
attr_accessor :autovalidation_check
|
||||
@autovalidation_check = false
|
||||
end
|
||||
|
||||
module Validation
|
||||
module AutoValidate
|
||||
|
||||
# # Force the auto validation for the class properties
|
||||
# # This feature is still not fully ported over,
|
||||
# # test are lacking, so please use with caution
|
||||
# def auto_validate!
|
||||
# auto_validation = true
|
||||
# end
|
||||
|
||||
# adds message for validator
|
||||
def options_with_message(base_options, property, validator_name)
|
||||
options = base_options.clone
|
||||
opts = property.options
|
||||
options[:message] = if opts[:messages]
|
||||
if opts[:messages].is_a?(Hash) and msg = opts[:messages][validator_name]
|
||||
msg
|
||||
else
|
||||
nil
|
||||
end
|
||||
elsif opts[:message]
|
||||
opts[:message]
|
||||
else
|
||||
nil
|
||||
end
|
||||
options
|
||||
end
|
||||
|
||||
|
||||
##
|
||||
# Auto-generate validations for a given property. This will only occur
|
||||
# if the option :auto_validation is either true or left undefined.
|
||||
#
|
||||
# @details [Triggers]
|
||||
# Triggers that generate validator creation
|
||||
#
|
||||
# :nullable => false
|
||||
# Setting the option :nullable to false causes a
|
||||
# validates_presence_of validator to be automatically created on
|
||||
# the property
|
||||
#
|
||||
# :size => 20 or :length => 20
|
||||
# Setting the option :size or :length causes a validates_length_of
|
||||
# validator to be automatically created on the property. If the
|
||||
# value is a Integer the validation will set :maximum => value if
|
||||
# the value is a Range the validation will set :within => value
|
||||
#
|
||||
# :format => :predefined / lambda / Proc
|
||||
# Setting the :format option causes a validates_format_of
|
||||
# validator to be automatically created on the property
|
||||
#
|
||||
# :set => ["foo", "bar", "baz"]
|
||||
# Setting the :set option causes a validates_within
|
||||
# validator to be automatically created on the property
|
||||
#
|
||||
# Integer type
|
||||
# Using a Integer type causes a validates_numericality_of
|
||||
# validator to be created for the property. integer_only
|
||||
# is set to true
|
||||
#
|
||||
# Float type
|
||||
# Using a Integer type causes a validates_is_number
|
||||
# validator to be created for the property. integer_only
|
||||
# is set to false, and precision/scale match the property
|
||||
#
|
||||
#
|
||||
# Messages
|
||||
#
|
||||
# :messages => {..}
|
||||
# Setting :messages hash replaces standard error messages
|
||||
# with custom ones. For instance:
|
||||
# :messages => {:presence => "Field is required",
|
||||
# :format => "Field has invalid format"}
|
||||
# Hash keys are: :presence, :format, :length, :is_unique,
|
||||
# :is_number, :is_primitive
|
||||
#
|
||||
# :message => "Some message"
|
||||
# It is just shortcut if only one validation option is set
|
||||
#
|
||||
def auto_generate_validations(property)
|
||||
return unless ((property.autovalidation_check != true) && self.auto_validation)
|
||||
return if (property.options && (property.options.has_key?(:auto_validation) && !property.options[:auto_validation]) || property.read_only)
|
||||
# value is set by the storage system
|
||||
opts = {}
|
||||
opts[:context] = property.options[:validates] if property.options.has_key?(:validates)
|
||||
|
||||
# presence
|
||||
if opts[:allow_nil] == false
|
||||
validates_presence_of property.name, options_with_message(opts, property, :presence)
|
||||
end
|
||||
|
||||
# length
|
||||
if property.type_class == String
|
||||
# XXX: maybe length should always return a Range, with the min defaulting to 1
|
||||
# 52 being the max set
|
||||
len = property.options.fetch(:length, property.options.fetch(:size, 52))
|
||||
if len.is_a?(Range)
|
||||
opts[:within] = len
|
||||
else
|
||||
opts[:maximum] = len
|
||||
end
|
||||
validates_length_of property.name, options_with_message(opts, property, :length)
|
||||
end
|
||||
|
||||
# format
|
||||
if property.options.has_key?(:format)
|
||||
opts[:with] = property.options[:format]
|
||||
# validates_format property.name, opts
|
||||
validates_format property.name, options_with_message(opts, property, :format)
|
||||
end
|
||||
|
||||
# uniqueness validator
|
||||
if property.options.has_key?(:unique)
|
||||
value = property.options[:unique]
|
||||
if value.is_a?(Array) || value.is_a?(Symbol)
|
||||
# validates_is_unique property.name, :scope => Array(value)
|
||||
validates_is_unique property.name, options_with_message({:scope => Array(value)}, property, :is_unique)
|
||||
elsif value.is_a?(TrueClass)
|
||||
# validates_is_unique property.name
|
||||
validates_is_unique property.name, options_with_message({}, property, :is_unique)
|
||||
end
|
||||
end
|
||||
|
||||
# within validator
|
||||
if property.options.has_key?(:set)
|
||||
validates_within property.name, options_with_message({:set => property.options[:set]}, property, :within)
|
||||
end
|
||||
|
||||
# numeric validator
|
||||
if property.type_class == Integer
|
||||
opts[:integer_only] = true
|
||||
validates_numericality_of property.name, options_with_message(opts, property, :is_number)
|
||||
elsif Float == property.type_class
|
||||
opts[:precision] = property.precision
|
||||
opts[:scale] = property.scale
|
||||
validates_numericality_of property.name, options_with_message(opts, property, :is_number)
|
||||
end
|
||||
|
||||
# marked the property as checked
|
||||
property.autovalidation_check = true
|
||||
|
||||
end
|
||||
|
||||
end # module AutoValidate
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,78 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class ContextualValidators
|
||||
|
||||
def dump
|
||||
contexts.each_pair do |key, context|
|
||||
puts "Key=#{key} Context: #{context}"
|
||||
end
|
||||
end
|
||||
|
||||
# Get a hash of named context validators for the resource
|
||||
#
|
||||
# @return <Hash> a hash of validators <GenericValidator>
|
||||
def contexts
|
||||
@contexts ||= {}
|
||||
end
|
||||
|
||||
# Return an array of validators for a named context
|
||||
#
|
||||
# @return <Array> An array of validators
|
||||
def context(name)
|
||||
contexts[name] ||= []
|
||||
end
|
||||
|
||||
# Clear all named context validators off of the resource
|
||||
#
|
||||
def clear!
|
||||
contexts.clear
|
||||
end
|
||||
|
||||
# Execute all validators in the named context against the target
|
||||
#
|
||||
# @param <Symbol> named_context the context we are validating against
|
||||
# @param <Object> target the resource that we are validating
|
||||
# @return <Boolean> true if all are valid, otherwise false
|
||||
def execute(named_context, target)
|
||||
raise(ArgumentError, 'invalid context specified') if !named_context || (contexts.length > 0 && !contexts[named_context])
|
||||
target.errors.clear!
|
||||
result = true
|
||||
context(named_context).each do |validator|
|
||||
next unless validator.execute?(target)
|
||||
result = false unless validator.call(target)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end # module ContextualValidators
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,125 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class ValidationErrors
|
||||
|
||||
include Enumerable
|
||||
|
||||
@@default_error_messages = {
|
||||
:absent => '%s must be absent',
|
||||
:inclusion => '%s must be one of [%s]',
|
||||
:invalid => '%s has an invalid format',
|
||||
:confirmation => '%s does not match the confirmation',
|
||||
:accepted => "%s is not accepted",
|
||||
:nil => '%s must not be nil',
|
||||
:blank => '%s must not be blank',
|
||||
:length_between => '%s must be between %s and %s characters long',
|
||||
:too_long => '%s must be less than %s characters long',
|
||||
:too_short => '%s must be more than %s characters long',
|
||||
:wrong_length => '%s must be %s characters long',
|
||||
:taken => '%s is already taken',
|
||||
:not_a_number => '%s must be a number',
|
||||
:not_an_integer => '%s must be an integer',
|
||||
:greater_than => '%s must be greater than %s',
|
||||
:greater_than_or_equal_to => "%s must be greater than or equal to %s",
|
||||
:equal_to => "%s must be equal to %s",
|
||||
:less_than => '%s must be less than %s',
|
||||
:less_than_or_equal_to => "%s must be less than or equal to %s",
|
||||
:value_between => '%s must be between %s and %s',
|
||||
:primitive => '%s must be of type %s'
|
||||
}
|
||||
|
||||
# Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
|
||||
cattr_writer :default_error_messages
|
||||
|
||||
def self.default_error_message(key, field, *values)
|
||||
field = field.to_s.humanize
|
||||
@@default_error_messages[key] % [field, *values].flatten
|
||||
end
|
||||
|
||||
# Clear existing validation errors.
|
||||
def clear!
|
||||
errors.clear
|
||||
end
|
||||
|
||||
# Add a validation error. Use the field_name :general if the errors does
|
||||
# not apply to a specific field of the Resource.
|
||||
#
|
||||
# @param <Symbol> field_name the name of the field that caused the error
|
||||
# @param <String> message the message to add
|
||||
def add(field_name, message)
|
||||
(errors[field_name.to_sym] ||= []) << message
|
||||
end
|
||||
|
||||
# Collect all errors into a single list.
|
||||
def full_messages
|
||||
errors.inject([]) do |list, pair|
|
||||
list += pair.last
|
||||
end
|
||||
end
|
||||
|
||||
# Return validation errors for a particular field_name.
|
||||
#
|
||||
# @param <Symbol> field_name the name of the field you want an error for
|
||||
def on(field_name)
|
||||
errors_for_field = errors[field_name.to_sym]
|
||||
errors_for_field.blank? ? nil : errors_for_field
|
||||
end
|
||||
|
||||
def each
|
||||
errors.map.each do |k, v|
|
||||
next if v.blank?
|
||||
yield(v)
|
||||
end
|
||||
end
|
||||
|
||||
def empty?
|
||||
entries.empty?
|
||||
end
|
||||
|
||||
# Return size of errors hash
|
||||
#
|
||||
# Allows us to play nicely with Rails' helpers
|
||||
def count
|
||||
errors.size
|
||||
end
|
||||
|
||||
def method_missing(meth, *args, &block)
|
||||
errors.send(meth, *args, &block)
|
||||
end
|
||||
|
||||
private
|
||||
def errors
|
||||
@errors ||= {}
|
||||
end
|
||||
|
||||
end # class ValidationErrors
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,74 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
class AbsentFieldValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.send(field_name)
|
||||
return true if (value.nil? || (value.respond_to?(:empty?) && value.empty?))
|
||||
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:absent, field_name)
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
return false
|
||||
end
|
||||
end # class AbsentFieldValidator
|
||||
|
||||
module ValidatesAbsent
|
||||
|
||||
##
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :unwanted_attribute, String
|
||||
# property :another_unwanted, String
|
||||
# property :yet_again, String
|
||||
#
|
||||
# validates_absent :unwanted_attribute
|
||||
# validates_absent :another_unwanted, :yet_again
|
||||
#
|
||||
# # a call to valid? will return false unless
|
||||
# # all three attributes are blank
|
||||
# end
|
||||
#
|
||||
def validates_absent(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::AbsentFieldValidator)
|
||||
end
|
||||
|
||||
end # module ValidatesAbsent
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,107 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class ConfirmationValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options = {})
|
||||
super
|
||||
@options = options
|
||||
@field_name, @confirm_field_name = field_name, (options[:confirm] || "#{field_name}_confirmation").to_sym
|
||||
@options[:allow_nil] = true unless @options.has_key?(:allow_nil)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
unless valid?(target)
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:confirmation, field_name)
|
||||
add_error(target, error_message, field_name)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def valid?(target)
|
||||
field_value = target.send(field_name)
|
||||
return true if @options[:allow_nil] && field_value.nil?
|
||||
return false if !@options[:allow_nil] && field_value.nil?
|
||||
|
||||
confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
|
||||
field_value == confirm_value
|
||||
end
|
||||
|
||||
end # class ConfirmationValidator
|
||||
|
||||
module ValidatesIsConfirmed
|
||||
|
||||
##
|
||||
# Validates that the given attribute is confirmed by another attribute.
|
||||
# A common use case scenario is when you require a user to confirm their
|
||||
# password, for which you use both password and password_confirmation
|
||||
# attributes.
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :confirm<Symbol> the attribute that you want to validate
|
||||
# against (default is firstattr_confirmation)
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page < Hash
|
||||
# include CouchRest::ExtendedModel
|
||||
# include CouchRest::Validations
|
||||
#
|
||||
# property :password, String
|
||||
# property :email, String
|
||||
# attr_accessor :password_confirmation
|
||||
# attr_accessor :email_repeated
|
||||
#
|
||||
# validates_confirmation_of :password
|
||||
# validates_confirmation_of :email, :confirm => :email_repeated
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # password == password_confirmation
|
||||
# # and
|
||||
# # email == email_repeated
|
||||
#
|
||||
def validates_confirmation_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::ConfirmationValidator)
|
||||
end
|
||||
|
||||
def validates_is_confirmed(*fields)
|
||||
warn "[DEPRECATION] `validates_is_confirmed` is deprecated. Please use `validates_confirmation_of` instead."
|
||||
validates_confirmation_of(*fields)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end # module ValidatesIsConfirmed
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,122 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
require 'pathname'
|
||||
require Pathname(__FILE__).dirname.expand_path + 'formats/email'
|
||||
require Pathname(__FILE__).dirname.expand_path + 'formats/url'
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class FormatValidator < GenericValidator
|
||||
|
||||
FORMATS = {}
|
||||
include CouchRest::Validation::Format::Email
|
||||
include CouchRest::Validation::Format::Url
|
||||
|
||||
def initialize(field_name, options = {}, &b)
|
||||
super(field_name, options)
|
||||
@field_name, @options = field_name, options
|
||||
@options[:allow_nil] = false unless @options.has_key?(:allow_nil)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.validation_property_value(field_name)
|
||||
return true if @options[:allow_nil] && value.nil?
|
||||
|
||||
validation = @options[:as] || @options[:with]
|
||||
|
||||
raise "No such predefined format '#{validation}'" if validation.is_a?(Symbol) && !FORMATS.has_key?(validation)
|
||||
validator = validation.is_a?(Symbol) ? FORMATS[validation][0] : validation
|
||||
|
||||
valid = case validator
|
||||
when Proc then validator.call(value)
|
||||
when Regexp then value =~ validator
|
||||
else
|
||||
raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{field_name} with #{validator.inspect}"
|
||||
end
|
||||
|
||||
return true if valid
|
||||
|
||||
error_message = @options[:message] || ValidationErrors.default_error_message(:invalid, field_name)
|
||||
|
||||
field = field_name.to_s.humanize
|
||||
error_message = error_message.call(field, value) if error_message.respond_to?(:call)
|
||||
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
#class UnknownValidationFormat < StandardError; end
|
||||
|
||||
end # class FormatValidator
|
||||
|
||||
module ValidatesFormat
|
||||
|
||||
##
|
||||
# Validates that the attribute is in the specified format. You may use the
|
||||
# :as (or :with, it's an alias) option to specify the pre-defined format
|
||||
# that you want to validate against. You may also specify your own format
|
||||
# via a Proc or Regexp passed to the the :as or :with options.
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :as<Format, Proc, Regexp> the pre-defined format, Proc or Regexp to validate against
|
||||
# @option :with<Format, Proc, Regexp> an alias for :as
|
||||
#
|
||||
# @details [Pre-defined Formats]
|
||||
# :email_address (format is specified in DataMapper::Validation::Format::Email)
|
||||
# :url (format is specified in DataMapper::Validation::Format::Url)
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :email, String
|
||||
# property :zip_code, String
|
||||
#
|
||||
# validates_format_of :email, :as => :email_address
|
||||
# validates_format_of :zip_code, :with => /^\d{5}$/
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # email is formatted like an email address
|
||||
# # and
|
||||
# # zip_code is a string of 5 digits
|
||||
#
|
||||
def validates_format_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::FormatValidator)
|
||||
end
|
||||
|
||||
def validates_format(*fields)
|
||||
warn "[DEPRECATION] `validates_format` is deprecated. Please use `validates_format_of` instead."
|
||||
validates_format_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesFormat
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,66 +0,0 @@
|
|||
# encoding: binary
|
||||
|
||||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
module Format
|
||||
module Email
|
||||
|
||||
def self.included(base)
|
||||
CouchRest::Validation::FormatValidator::FORMATS.merge!(
|
||||
:email_address => [ EmailAddress, lambda { |field, value| '%s is not a valid email address'.t(value) }]
|
||||
)
|
||||
end
|
||||
|
||||
# RFC2822 (No attribution reference available)
|
||||
EmailAddress = begin
|
||||
alpha = "a-zA-Z"
|
||||
digit = "0-9"
|
||||
atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
|
||||
dot_atom_text = "#{atext}+([.]#{atext}*)*"
|
||||
dot_atom = "#{dot_atom_text}"
|
||||
qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
|
||||
text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
|
||||
quoted_pair = "(\\x5c#{text})"
|
||||
qcontent = "(?:#{qtext}|#{quoted_pair})"
|
||||
quoted_string = "[\"]#{qcontent}+[\"]"
|
||||
atom = "#{atext}+"
|
||||
word = "(?:#{atom}|#{quoted_string})"
|
||||
obs_local_part = "#{word}([.]#{word})*"
|
||||
local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
|
||||
no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
|
||||
dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
|
||||
dcontent = "(?:#{dtext}|#{quoted_pair})"
|
||||
domain_literal = "\\[#{dcontent}+\\]"
|
||||
obs_domain = "#{atom}([.]#{atom})*"
|
||||
domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
|
||||
addr_spec = "#{local_part}\@#{domain}"
|
||||
pattern = /^#{addr_spec}$/
|
||||
end
|
||||
|
||||
end # module Email
|
||||
end # module Format
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,43 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
module Format
|
||||
module Url
|
||||
|
||||
def self.included(base)
|
||||
CouchRest::Validation::FormatValidator::FORMATS.merge!(
|
||||
:url => [ Url, lambda { |field, value| '%s is not a valid URL'.t(value) }]
|
||||
)
|
||||
end
|
||||
|
||||
Url = begin
|
||||
# Regex from http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
|
||||
/(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix
|
||||
end
|
||||
|
||||
end # module Url
|
||||
end # module Format
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,120 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
# All validators extend this base class. Validators must:
|
||||
#
|
||||
# * Implement the initialize method to capture its parameters, also calling
|
||||
# super to have this parent class capture the optional, general :if and
|
||||
# :unless parameters.
|
||||
# * Implement the call method, returning true or false. The call method
|
||||
# provides the validation logic.
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
class GenericValidator
|
||||
|
||||
attr_accessor :if_clause, :unless_clause
|
||||
attr_reader :field_name
|
||||
|
||||
# Construct a validator. Capture the :if and :unless clauses when present.
|
||||
#
|
||||
# @param field<String, Symbol> The property specified for validation
|
||||
#
|
||||
# @option :if<Symbol, Proc> The name of a method or a Proc to call to
|
||||
# determine if the validation should occur.
|
||||
# @option :unless<Symbol, Proc> The name of a method or a Proc to call to
|
||||
# determine if the validation should not occur
|
||||
# All additional key/value pairs are passed through to the validator
|
||||
# that is sub-classing this GenericValidator
|
||||
#
|
||||
def initialize(field, opts = {})
|
||||
@if_clause = opts.delete(:if)
|
||||
@unless_clause = opts.delete(:unless)
|
||||
end
|
||||
|
||||
# Add an error message to a target resource. If the error corresponds to a
|
||||
# specific field of the resource, add it to that field, otherwise add it
|
||||
# as a :general message.
|
||||
#
|
||||
# @param <Object> target the resource that has the error
|
||||
# @param <String> message the message to add
|
||||
# @param <Symbol> field_name the name of the field that caused the error
|
||||
#
|
||||
# TODO - should the field_name for a general message be :default???
|
||||
#
|
||||
def add_error(target, message, field_name = :general)
|
||||
target.errors.add(field_name, message)
|
||||
end
|
||||
|
||||
# Call the validator. "call" is used so the operation is BoundMethod and
|
||||
# Block compatible. This must be implemented in all concrete classes.
|
||||
#
|
||||
# @param <Object> target the resource that the validator must be called
|
||||
# against
|
||||
# @return <Boolean> true if valid, otherwise false
|
||||
def call(target)
|
||||
raise NotImplementedError, "CouchRest::Validation::GenericValidator::call must be overriden in a subclass"
|
||||
end
|
||||
|
||||
# Determines if this validator should be run against the
|
||||
# target by evaluating the :if and :unless clauses
|
||||
# optionally passed while specifying any validator.
|
||||
#
|
||||
# @param <Object> target the resource that we check against
|
||||
# @return <Boolean> true if should be run, otherwise false
|
||||
def execute?(target)
|
||||
if unless_clause = self.unless_clause
|
||||
if unless_clause.is_a?(Symbol)
|
||||
return false if target.send(unless_clause)
|
||||
elsif unless_clause.respond_to?(:call)
|
||||
return false if unless_clause.call(target)
|
||||
end
|
||||
end
|
||||
|
||||
if if_clause = self.if_clause
|
||||
if if_clause.is_a?(Symbol)
|
||||
return target.send(if_clause)
|
||||
elsif if_clause.respond_to?(:call)
|
||||
return if_clause.call(target)
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
self.class == other.class &&
|
||||
self.field_name == other.field_name &&
|
||||
self.class == other.class &&
|
||||
self.if_clause == other.if_clause &&
|
||||
self.unless_clause == other.unless_clause &&
|
||||
self.instance_variable_get(:@options) == other.instance_variable_get(:@options)
|
||||
end
|
||||
|
||||
end # class GenericValidator
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,139 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class LengthValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options)
|
||||
super
|
||||
@field_name = field_name
|
||||
@options = options
|
||||
|
||||
@min = options[:minimum] || options[:min]
|
||||
@max = options[:maximum] || options[:max]
|
||||
@equal = options[:is] || options[:equals]
|
||||
@range = options[:within] || options[:in]
|
||||
|
||||
@validation_method ||= :range if @range
|
||||
@validation_method ||= :min if @min && @max.nil?
|
||||
@validation_method ||= :max if @max && @min.nil?
|
||||
@validation_method ||= :equals unless @equal.nil?
|
||||
end
|
||||
|
||||
def call(target)
|
||||
field_value = target.validation_property_value(field_name)
|
||||
return true if @options[:allow_nil] && field_value.nil?
|
||||
|
||||
field_value = '' if field_value.nil?
|
||||
|
||||
# XXX: HACK seems hacky to do this on every validation, probably should
|
||||
# do this elsewhere?
|
||||
field = field_name.to_s.humanize
|
||||
min = @range ? @range.min : @min
|
||||
max = @range ? @range.max : @max
|
||||
equal = @equal
|
||||
|
||||
case @validation_method
|
||||
when :range then
|
||||
unless valid = @range.include?(field_value.size)
|
||||
error_message = ValidationErrors.default_error_message(:length_between, field, min, max)
|
||||
end
|
||||
when :min then
|
||||
unless valid = field_value.size >= min
|
||||
error_message = ValidationErrors.default_error_message(:too_short, field, min)
|
||||
end
|
||||
when :max then
|
||||
unless valid = field_value.size <= max
|
||||
error_message = ValidationErrors.default_error_message(:too_long, field, max)
|
||||
end
|
||||
when :equals then
|
||||
unless valid = field_value.size == equal
|
||||
error_message = ValidationErrors.default_error_message(:wrong_length, field, equal)
|
||||
end
|
||||
end
|
||||
|
||||
error_message = @options[:message] || error_message
|
||||
|
||||
add_error(target, error_message, field_name) unless valid
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
end # class LengthValidator
|
||||
|
||||
module ValidatesLength
|
||||
|
||||
# Validates that the length of the attribute is equal to, less than,
|
||||
# greater than or within a certain range (depending upon the options
|
||||
# you specify).
|
||||
#
|
||||
# @option :allow_nil<Boolean> true/false (default is true)
|
||||
# @option :minimum ensures that the attribute's length is greater than
|
||||
# or equal to the supplied value
|
||||
# @option :min alias for :minimum
|
||||
# @option :maximum ensures the attribute's length is less than or equal
|
||||
# to the supplied value
|
||||
# @option :max alias for :maximum
|
||||
# @option :equals ensures the attribute's length is equal to the
|
||||
# supplied value
|
||||
# @option :is alias for :equals
|
||||
# @option :in<Range> given a Range, ensures that the attributes length is
|
||||
# include?'ed in the Range
|
||||
# @option :within<Range> alias for :in
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property high, Integer
|
||||
# property low, Integer
|
||||
# property just_right, Integer
|
||||
#
|
||||
# validates_length_of :high, :min => 100000000000
|
||||
# validates_length_of :low, :equals => 0
|
||||
# validates_length_of :just_right, :within => 1..10
|
||||
#
|
||||
# # a call to valid? will return false unless:
|
||||
# # high is greater than or equal to 100000000000
|
||||
# # low is equal to 0
|
||||
# # just_right is between 1 and 10 (inclusive of both 1 and 10)
|
||||
#
|
||||
def validates_length_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::LengthValidator)
|
||||
end
|
||||
|
||||
def validates_length(*fields)
|
||||
warn "[DEPRECATION] `validates_length` is deprecated. Please use `validates_length_of` instead."
|
||||
validates_length_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesLength
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,89 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class MethodValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options.clone
|
||||
@options[:method] = @field_name unless @options.has_key?(:method)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
result, message = target.send(@options[:method])
|
||||
add_error(target, message, field_name) unless result
|
||||
result
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
@options[:method] == other.instance_variable_get(:@options)[:method] && super
|
||||
end
|
||||
end # class MethodValidator
|
||||
|
||||
module ValidatesWithMethod
|
||||
|
||||
##
|
||||
# Validate using the given method. The method given needs to return:
|
||||
# [result::<Boolean>, Error Message::<String>]
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :zip_code, String
|
||||
#
|
||||
# validates_with_method :in_the_right_location?
|
||||
#
|
||||
# def in_the_right_location?
|
||||
# if @zip_code == "94301"
|
||||
# return true
|
||||
# else
|
||||
# return [false, "You're in the wrong zip code"]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # A call to valid? will return false and
|
||||
# # populate the object's errors with "You're in the
|
||||
# # wrong zip code" unless zip_code == "94301"
|
||||
#
|
||||
# # You can also specify field:
|
||||
#
|
||||
# validates_with_method :zip_code, :in_the_right_location?
|
||||
#
|
||||
# # it will add returned error message to :zip_code field
|
||||
#
|
||||
def validates_with_method(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::MethodValidator)
|
||||
end
|
||||
|
||||
end # module ValidatesWithMethod
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,109 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class NumericValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
@options[:integer_only] = false unless @options.has_key?(:integer_only)
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.send(field_name)
|
||||
return true if @options[:allow_nil] && value.nil?
|
||||
|
||||
value = (defined?(BigDecimal) && value.kind_of?(BigDecimal)) ? value.to_s('F') : value.to_s
|
||||
|
||||
error_message = @options[:message]
|
||||
precision = @options[:precision]
|
||||
scale = @options[:scale]
|
||||
|
||||
if @options[:integer_only]
|
||||
return true if value =~ /\A[+-]?\d+\z/
|
||||
error_message ||= ValidationErrors.default_error_message(:not_an_integer, field_name)
|
||||
else
|
||||
# FIXME: if precision and scale are not specified, can we assume that it is an integer?
|
||||
# probably not, as floating point numbers don't have hard
|
||||
# defined scale. the scale floats with the length of the
|
||||
# integral and precision. Ie. if precision = 10 and integral
|
||||
# portion of the number is 9834 (4 digits), the max scale will
|
||||
# be 6 (10 - 4). But if the integral length is 1, max scale
|
||||
# will be (10 - 1) = 9, so 1.234567890.
|
||||
if precision && scale
|
||||
#handles both Float when it has scale specified and BigDecimal
|
||||
if precision > scale && scale > 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision - scale}}|\d{0,#{precision - scale}}\.\d{1,#{scale}})\z/
|
||||
elsif precision > scale && scale == 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
|
||||
elsif precision == scale
|
||||
return true if value =~ /\A[+-]?(?:0(?:\.\d{1,#{scale}})?)\z/
|
||||
else
|
||||
raise ArgumentError, "Invalid precision #{precision.inspect} and scale #{scale.inspect} for #{field_name} (value: #{value.inspect} #{value.class})"
|
||||
end
|
||||
elsif precision && scale.nil?
|
||||
# for floats, if scale is not set
|
||||
|
||||
#total number of digits is less or equal precision
|
||||
return true if value.gsub(/[^\d]/, '').length <= precision
|
||||
|
||||
#number of digits before decimal == precision, and the number is x.0. same as scale = 0
|
||||
return true if value =~ /\A[+-]?(?:\d{1,#{precision}}(?:\.0)?)\z/
|
||||
else
|
||||
return true if value =~ /\A[+-]?(?:\d+|\d*\.\d+)\z/
|
||||
end
|
||||
error_message ||= ValidationErrors.default_error_message(:not_a_number, field_name)
|
||||
end
|
||||
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
# TODO: check the gt, gte, lt, lte, and eq options
|
||||
|
||||
return false
|
||||
end
|
||||
end # class NumericValidator
|
||||
|
||||
module ValidatesIsNumber
|
||||
|
||||
# Validate whether a field is numeric
|
||||
#
|
||||
def validates_numericality_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::NumericValidator)
|
||||
end
|
||||
|
||||
def validates_is_number(*fields)
|
||||
warn "[DEPRECATION] `validates_is_number` is deprecated. Please use `validates_numericality_of` instead."
|
||||
validates_numericality_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesIsNumber
|
||||
end # module Validation
|
||||
end # module CouchRest
|
|
@ -1,114 +0,0 @@
|
|||
# Extracted from dm-validations 0.9.10
|
||||
#
|
||||
# Copyright (c) 2007 Guy van den Berg
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module CouchRest
|
||||
module Validation
|
||||
|
||||
##
|
||||
#
|
||||
# @author Guy van den Berg
|
||||
# @since 0.9
|
||||
class RequiredFieldValidator < GenericValidator
|
||||
|
||||
def initialize(field_name, options={})
|
||||
super
|
||||
@field_name, @options = field_name, options
|
||||
end
|
||||
|
||||
def call(target)
|
||||
value = target.validation_property_value(field_name)
|
||||
property = target.validation_property(field_name.to_s)
|
||||
return true if present?(value, property)
|
||||
|
||||
error_message = @options[:message] || default_error(property)
|
||||
add_error(target, error_message, field_name)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Boolean property types are considered present if non-nil.
|
||||
# Other property types are considered present if non-blank.
|
||||
# Non-properties are considered present if non-blank.
|
||||
def present?(value, property)
|
||||
boolean_type?(property) ? !value.nil? : !value.blank?
|
||||
end
|
||||
|
||||
def default_error(property)
|
||||
actual = boolean_type?(property) ? :nil : :blank
|
||||
ValidationErrors.default_error_message(actual, field_name)
|
||||
end
|
||||
|
||||
# Is +property+ a boolean property?
|
||||
#
|
||||
# Returns true for Boolean, ParanoidBoolean, TrueClass, etc. properties.
|
||||
# Returns false for other property types.
|
||||
# Returns false for non-properties.
|
||||
def boolean_type?(property)
|
||||
property ? property.type == 'Boolean' : false
|
||||
end
|
||||
|
||||
end # class RequiredFieldValidator
|
||||
|
||||
module ValidatesPresent
|
||||
|
||||
##
|
||||
# Validates that the specified attribute is present.
|
||||
#
|
||||
# For most property types "being present" is the same as being "not
|
||||
# blank" as determined by the attribute's #blank? method. However, in
|
||||
# the case of Boolean, "being present" means not nil; i.e. true or
|
||||
# false.
|
||||
#
|
||||
# @note
|
||||
# dm-core's support lib adds the blank? method to many classes,
|
||||
# @see lib/dm-core/support/blank.rb (dm-core) for more information.
|
||||
#
|
||||
# @example [Usage]
|
||||
#
|
||||
# class Page
|
||||
#
|
||||
# property :required_attribute, String
|
||||
# property :another_required, String
|
||||
# property :yet_again, String
|
||||
#
|
||||
# validates_presence_of :required_attribute
|
||||
# validates_presence_of :another_required, :yet_again
|
||||
#
|
||||
# # a call to valid? will return false unless
|
||||
# # all three attributes are !blank?
|
||||
# end
|
||||
def validates_presence_of(*fields)
|
||||
opts = opts_from_validator_args(fields)
|
||||
add_validator_to_context(opts, fields, CouchRest::Validation::RequiredFieldValidator)
|
||||
end
|
||||
|
||||
def validates_present(*fields)
|
||||
warn "[DEPRECATION] `validates_present` is deprecated. Please use `validates_presence_of` instead."
|
||||
validates_presence_of(*fields)
|
||||
end
|
||||
|
||||
end # module ValidatesPresent
|
||||
end # module Validation
|
||||
end # module CouchRest
|
Loading…
Add table
Add a link
Reference in a new issue