Merge branch 'master' of git://github.com/couchrest/couchrest_model

This commit is contained in:
Lucas Renan 2011-06-21 14:03:04 -03:00
commit 9f7ce5d49b
62 changed files with 621 additions and 322 deletions

View file

@ -1 +1 @@
1.1.0.beta5 1.1.0.rc1

View file

@ -23,11 +23,12 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.add_dependency(%q<couchrest>, "1.1.0.pre2") s.add_dependency(%q<couchrest>, "1.1.0.pre3")
s.add_dependency(%q<mime-types>, "~> 1.15") s.add_dependency(%q<mime-types>, "~> 1.15")
s.add_dependency(%q<activemodel>, "~> 3.0") s.add_dependency(%q<activemodel>, "~> 3.0")
s.add_dependency(%q<tzinfo>, "~> 0.3.22") s.add_dependency(%q<tzinfo>, "~> 0.3.22")
s.add_development_dependency(%q<rspec>, ">= 2.0.0") s.add_development_dependency(%q<rspec>, "~> 2.6.0")
s.add_development_dependency(%q<json>, ["~> 1.5.1"])
s.add_development_dependency(%q<rack-test>, ">= 0.5.7") s.add_development_dependency(%q<rack-test>, ">= 0.5.7")
# s.add_development_dependency("jruby-openssl", ">= 0.7.3") # s.add_development_dependency("jruby-openssl", ">= 0.7.3")
end end

View file

@ -1,10 +1,18 @@
# CouchRest Model Change History # CouchRest Model Change History
## 1.1.0 - 2011-05-XX ## 1.1.0 - 2011-06-XX
* Minor Fixes
* Validation callbacks now support context (thanks kostia)
* Document comparisons now performed using database and document ID (pointer by neocsr)
## 1.1.0.rc1 - 2011-06-08
* New Features * New Features
* Properties with a nil value are now no longer sent to the database. * Properties with a nil value are now no longer sent to the database.
* Now possible to build new objects via CastedArray#build * Now possible to build new objects via CastedArray#build
* Implement #get! and #find! class methods
* Now is possible delete particular elements in casted array(Kostiantyn Kahanskyi)
* Minor fixes * Minor fixes
* #as_json now correctly uses ActiveSupports methods. * #as_json now correctly uses ActiveSupports methods.
@ -14,7 +22,12 @@
* DesignDoc cache refreshed if a database is deleted. * DesignDoc cache refreshed if a database is deleted.
* Fixing dirty tracking on collection_of association. * Fixing dirty tracking on collection_of association.
* Uniqueness Validation views created on initialization, not on demand! * Uniqueness Validation views created on initialization, not on demand!
* #destroy freezes object instead of removing _id and _rev, better for callbacks (pointer by karmi)
* #destroyed? method now available
* #reload no longer uses Hash#merge! which was causing issues with dirty tracking on casted models. (pointer by kostia)
* Non-property mass assignment on #new no longer possible without :directly_set_attributes option.
* Using CouchRest 1.1.0.pre3. (No more Hashes!)
* Fixing problem assigning a CastedHash to a property declared as a Hash (Kostiantyn Kahanskyi, gfmtim)
## 1.1.0.beta5 - 2011-04-30 ## 1.1.0.beta5 - 2011-04-30

View file

@ -1,13 +1,12 @@
module CouchRest module CouchRest
module Model module Model
class Base < Document class Base < CouchRest::Document
extend ActiveModel::Naming extend ActiveModel::Naming
include CouchRest::Model::Configuration include CouchRest::Model::Configuration
include CouchRest::Model::Connection include CouchRest::Model::Connection
include CouchRest::Model::Persistence include CouchRest::Model::Persistence
include CouchRest::Model::Callbacks
include CouchRest::Model::DocumentQueries include CouchRest::Model::DocumentQueries
include CouchRest::Model::Views include CouchRest::Model::Views
include CouchRest::Model::DesignDoc include CouchRest::Model::DesignDoc
@ -18,9 +17,11 @@ module CouchRest
include CouchRest::Model::PropertyProtection include CouchRest::Model::PropertyProtection
include CouchRest::Model::Associations include CouchRest::Model::Associations
include CouchRest::Model::Validations include CouchRest::Model::Validations
include CouchRest::Model::Callbacks
include CouchRest::Model::Designs include CouchRest::Model::Designs
include CouchRest::Model::CastedBy include CouchRest::Model::CastedBy
include CouchRest::Model::Dirty include CouchRest::Model::Dirty
include CouchRest::Model::Callbacks
def self.subclasses def self.subclasses
@subclasses ||= [] @subclasses ||= []
@ -51,14 +52,15 @@ module CouchRest
# #
# If a block is provided the new model will be passed into the # If a block is provided the new model will be passed into the
# block so that it can be populated. # block so that it can be populated.
def initialize(doc = {}, options = {}) def initialize(attributes = {}, options = {})
doc = prepare_all_attributes(doc, options) super()
# set the instances database, if provided prepare_all_attributes(attributes, options)
# set the instance's database, if provided
self.database = options[:database] unless options[:database].nil? self.database = options[:database] unless options[:database].nil?
super(doc)
unless self['_id'] && self['_rev'] unless self['_id'] && self['_rev']
self[self.model_type_key] = self.class.to_s self[self.model_type_key] = self.class.to_s
end end
yield self if block_given? yield self if block_given?
after_initialize if respond_to?(:after_initialize) after_initialize if respond_to?(:after_initialize)
@ -79,18 +81,8 @@ module CouchRest
super super
end end
## Compatibility with ActiveSupport and older frameworks
# Hack so that CouchRest::Document, which descends from Hash,
# doesn't appear to Rails routing as a Hash of options
def is_a?(klass)
return false if klass == Hash
super
end
alias :kind_of? :is_a?
def persisted? def persisted?
!new? !new? && !destroyed?
end end
def to_key def to_key
@ -100,6 +92,21 @@ module CouchRest
alias :to_param :id alias :to_param :id
alias :new_record? :new? alias :new_record? :new?
alias :new_document? :new? alias :new_document? :new?
# Compare this model with another by confirming to see
# if the IDs and their databases match!
#
# Camparison of the database is required in case the
# model has been proxied or loaded elsewhere.
#
# A Basic CouchRest document will only ever compare using
# a Hash comparison on the attributes.
def == other
return false unless other.is_a?(Base)
database == other.database && id == other.id
end
alias :eql? :==
end end
end end
end end

View file

@ -5,21 +5,23 @@ module CouchRest #:nodoc:
module Callbacks module Callbacks
extend ActiveSupport::Concern extend ActiveSupport::Concern
CALLBACKS = [
:before_validation, :after_validation,
:after_initialize,
:before_create, :around_create, :after_create,
:before_destroy, :around_destroy, :after_destroy,
:before_save, :around_save, :after_save,
:before_update, :around_update, :after_update,
]
included do included do
extend ActiveModel::Callbacks extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
define_model_callbacks \ define_model_callbacks :initialize, :only => :after
:create, define_model_callbacks :create, :destroy, :save, :update
:destroy,
:save,
:update
end end
def valid?(*) #nodoc
_run_validation_callbacks { super }
end
end end
end end

View file

@ -50,6 +50,16 @@ module CouchRest::Model
super super
end end
def delete(obj)
couchrest_parent_will_change! if use_dirty? && self.length > 0
super(obj)
end
def delete_at(index)
couchrest_parent_will_change! if use_dirty? && self.length > 0
super(index)
end
def build(*args) def build(*args)
obj = casted_by_property.build(*args) obj = casted_by_property.build(*args)
self.push(obj) self.push(obj)

View file

@ -5,13 +5,15 @@ module CouchRest::Model
included do included do
include CouchRest::Model::Configuration include CouchRest::Model::Configuration
include CouchRest::Model::Callbacks
include CouchRest::Model::Properties include CouchRest::Model::Properties
include CouchRest::Model::PropertyProtection include CouchRest::Model::PropertyProtection
include CouchRest::Model::Associations include CouchRest::Model::Associations
include CouchRest::Model::Validations include CouchRest::Model::Validations
include CouchRest::Model::Callbacks
include CouchRest::Model::CastedBy include CouchRest::Model::CastedBy
include CouchRest::Model::Dirty include CouchRest::Model::Dirty
include CouchRest::Model::Callbacks
class_eval do class_eval do
# Override CastedBy's base_doc? # Override CastedBy's base_doc?
def base_doc? def base_doc?
@ -42,7 +44,7 @@ module CouchRest::Model
alias :new_record? :new? alias :new_record? :new?
def persisted? def persisted?
!new? !new? && !destroyed?
end end
# The to_param method is needed for rails to generate resourceful routes. # The to_param method is needed for rails to generate resourceful routes.

View file

@ -87,7 +87,14 @@ module CouchRest
doc doc
end end
alias :find :get alias :find :get
def get!(id)
doc = @klass.get!(id, @database)
doc.database = @database if doc && doc.respond_to?(:database)
doc
end
alias :find! :get!
# Views # Views
def has_view?(view) def has_view?(view)

View file

@ -244,6 +244,7 @@ module CouchRest
else else
options = { :limit => per_page, :skip => per_page * (page - 1) } options = { :limit => per_page, :skip => per_page * (page - 1) }
end end
options[:include_docs] = true
view_options.merge(options) view_options.merge(options)
end end

View file

@ -1,13 +1,10 @@
module CouchRest module CouchRest
module Model module Model
module DocumentQueries module DocumentQueries
extend ActiveSupport::Concern
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods module ClassMethods
# Load all documents that have the model_type_key's field equal to the # Load all documents that have the model_type_key's field equal to the
# name of the current class. Take the standard set of # name of the current class. Take the standard set of
# CouchRest::Database#view options. # CouchRest::Database#view options.
@ -73,7 +70,7 @@ module CouchRest
end end
end end
alias :find :get alias :find :get
# Load a document from the database by id # Load a document from the database by id
# An exception will be raised if the document isn't found # An exception will be raised if the document isn't found
# #
@ -86,9 +83,12 @@ module CouchRest
# id<String, Integer>:: Document ID # id<String, Integer>:: Document ID
# db<Database>:: optional option to pass a custom database to use # db<Database>:: optional option to pass a custom database to use
def get!(id, db = database) def get!(id, db = database)
raise "Missing or empty document ID" if id.to_s.empty? raise CouchRest::Model::DocumentNotFound if id.blank?
doc = db.get id doc = db.get id
build_from_database(doc) build_from_database(doc)
rescue RestClient::ResourceNotFound
raise CouchRest::Model::DocumentNotFound
end end
alias :find! :get! alias :find! :get!

View file

@ -19,5 +19,7 @@ module CouchRest
end end
end end
end end
class DocumentNotFound < Errors::CouchRestModelError; end
end end
end end

View file

@ -21,8 +21,8 @@ module CouchRest
# Creates the document in the db. Raises an exception # Creates the document in the db. Raises an exception
# if the document is not created properly. # if the document is not created properly.
def create! def create!(options = {})
self.class.fail_validate!(self) unless self.create self.class.fail_validate!(self) unless self.create(options)
end end
# Trigger the callbacks (before, after, around) # Trigger the callbacks (before, after, around)
@ -54,19 +54,21 @@ module CouchRest
end end
# Deletes the document from the database. Runs the :destroy callbacks. # 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 def destroy
_run_destroy_callbacks do _run_destroy_callbacks do
result = database.delete_doc(self) result = database.delete_doc(self)
if result['ok'] if result['ok']
self.delete('_rev') @_destroyed = true
self.delete('_id') self.freeze
end end
result['ok'] result['ok']
end end
end end
def destroyed?
!!@_destroyed
end
# Update the document's attributes and save. For example: # Update the document's attributes and save. For example:
# #
# doc.update_attributes :name => "Fred" # doc.update_attributes :name => "Fred"
@ -85,7 +87,7 @@ module CouchRest
# #
# Returns self. # Returns self.
def reload def reload
merge!(self.class.get(id)) prepare_all_attributes(database.get(id), :directly_set_attributes => true)
self self
end end
@ -104,22 +106,25 @@ module CouchRest
module ClassMethods module ClassMethods
# Creates a new instance, bypassing attribute protection # Creates a new instance, bypassing attribute protection and
# uses the type field to determine which model to use to instanatiate
# the new object.
# #
# ==== Returns # ==== Returns
# a document instance # a document instance
# #
def build_from_database(doc = {}) def build_from_database(doc = {}, options = {}, &block)
base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize src = doc[model_type_key]
base.new(doc, :directly_set_attributes => true) base = (src.blank? || src == self.to_s) ? self : src.constantize
base.new(doc, options.merge(:directly_set_attributes => true), &block)
end end
# Defines an instance and save it directly to the database # Defines an instance and save it directly to the database
# #
# ==== Returns # ==== Returns
# returns the reloaded document # returns the reloaded document
def create(attributes = {}) def create(attributes = {}, &block)
instance = new(attributes) instance = new(attributes, &block)
instance.create instance.create
instance instance
end end
@ -128,8 +133,8 @@ module CouchRest
# #
# ==== Returns # ==== Returns
# returns the reloaded document or raises an exception # returns the reloaded document or raises an exception
def create!(attributes = {}) def create!(attributes = {}, &block)
instance = new(attributes) instance = new(attributes, &block)
instance.create! instance.create!
instance instance
end end

View file

@ -80,17 +80,18 @@ module CouchRest
self.disable_dirty = dirty self.disable_dirty = dirty
end end
def prepare_all_attributes(doc = {}, options = {}) def prepare_all_attributes(attrs = {}, options = {})
self.disable_dirty = !!options[:directly_set_attributes] self.disable_dirty = !!options[:directly_set_attributes]
apply_all_property_defaults apply_all_property_defaults
if options[:directly_set_attributes] if options[:directly_set_attributes]
directly_set_read_only_attributes(doc) directly_set_read_only_attributes(attrs)
directly_set_attributes(attrs, true)
else else
doc = remove_protected_attributes(doc) attrs = remove_protected_attributes(attrs)
directly_set_attributes(attrs)
end end
res = doc.nil? ? doc : directly_set_attributes(doc)
self.disable_dirty = false self.disable_dirty = false
res self
end end
def find_property!(property) def find_property!(property)
@ -101,16 +102,13 @@ module CouchRest
# Set all the attributes and return a hash with the attributes # Set all the attributes and return a hash with the attributes
# that have not been accepted. # that have not been accepted.
def directly_set_attributes(hash) def directly_set_attributes(hash, mass_assign = false)
hash.reject do |attribute_name, attribute_value| return if hash.nil?
if self.respond_to?("#{attribute_name}=") hash.reject do |key, value|
self.send("#{attribute_name}=", attribute_value) if self.respond_to?("#{key}=")
true self.send("#{key}=", value)
elsif mass_assign_any_attribute # config option elsif mass_assign || mass_assign_any_attribute
self[attribute_name] = attribute_value self[key] = value
true
else
false
end end
end end
end end

View file

@ -27,19 +27,15 @@ module CouchRest::Model
if value.nil? if value.nil?
value = [] value = []
elsif [Hash, HashWithIndifferentAccess].include?(value.class) elsif [Hash, HashWithIndifferentAccess].include?(value.class)
# Assume provided as a Hash where key is index! # Assume provided as a params hash where key is index
data = value value = parameter_hash_to_array(value)
value = [ ]
data.keys.sort.each do |k|
value << data[k]
end
elsif !value.is_a?(Array) elsif !value.is_a?(Array)
raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}" raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
end end
arr = value.collect { |data| cast_value(parent, data) } arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray # allow casted_by calls to be passed up chain by wrapping in CastedArray
CastedArray.new(arr, self, parent) CastedArray.new(arr, self, parent)
elsif (type == Object || type == Hash) && (value.class == Hash) elsif (type == Object || type == Hash) && (value.is_a?(Hash))
# allow casted_by calls to be passed up chain by wrapping in CastedHash # allow casted_by calls to be passed up chain by wrapping in CastedHash
CastedHash[value, self, parent] CastedHash[value, self, parent]
elsif !value.nil? elsif !value.nil?
@ -78,6 +74,14 @@ module CouchRest::Model
private private
def parameter_hash_to_array(source)
value = [ ]
source.keys.each do |k|
value[k.to_i] = source[k]
end
value.compact
end
def associate_casted_value_to_parent(parent, value) def associate_casted_value_to_parent(parent, value)
value.casted_by = parent if value.respond_to?(:casted_by) value.casted_by = parent if value.respond_to?(:casted_by)
value.casted_by_property = self if value.respond_to?(:casted_by_property) value.casted_by_property = self if value.respond_to?(:casted_by_property)

View file

@ -73,12 +73,12 @@ module CouchRest
end end
# Base # Base
def new(*args) def new(attrs = {}, options = {}, &block)
proxy_update(model.new(*args)) proxy_block_update(:new, attrs, options, &block)
end end
def build_from_database(doc = {}) def build_from_database(attrs = {}, options = {}, &block)
proxy_update(model.build_from_database(doc)) proxy_block_update(:build_from_database, attrs, options, &block)
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
@ -170,6 +170,13 @@ module CouchRest
end end
end end
def proxy_block_update(method, *args, &block)
model.send(method, *args) do |doc|
proxy_update(doc)
yield doc if block_given?
end
end
end end
end end
end end

View file

@ -18,7 +18,7 @@ CouchRest::Design.class_eval do
flatten = flatten =
lambda {|r| lambda {|r|
(recurse = lambda {|v| (recurse = lambda {|v|
if v.is_a?(Hash) if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
v.to_a.map{|v| recurse.call(v)}.flatten v.to_a.map{|v| recurse.call(v)}.flatten
elsif v.is_a?(Array) elsif v.is_a?(Array)
v.flatten.map{|v| recurse.call(v)} v.flatten.map{|v| recurse.call(v)}

View file

@ -13,22 +13,33 @@ module CouchRest
# Validations may be applied to both Model::Base and Model::CastedModel # Validations may be applied to both Model::Base and Model::CastedModel
module Validations module Validations
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do include ActiveModel::Validations
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks # Determine if the document is valid.
#
# @example Is the document valid?
# person.valid?
#
# @example Is the document valid in a context?
# person.valid?(:create)
#
# @param [ Symbol ] context The optional validation context.
#
# @return [ true, false ] True if valid, false if not.
#
def valid?(context = nil)
super context ? context : (new? ? :create : :update)
end end
module ClassMethods module ClassMethods
# Validates the associated casted model. This method should not be # Validates the associated casted model. This method should not be
# used within your code as it is automatically included when a CastedModel # used within your code as it is automatically included when a CastedModel
# is used inside the model. # is used inside the model.
#
def validates_casted_model(*args) def validates_casted_model(*args)
validates_with(CastedModelValidator, _merge_attributes(args)) validates_with(CastedModelValidator, _merge_attributes(args))
end end
# Validates if the field is unique for this type of document. Automatically creates # Validates if the field is unique for this type of document. Automatically creates
# a view if one does not already exist and performs a search for all matching # a view if one does not already exist and performs a search for all matching
# documents. # documents.

View file

@ -60,8 +60,8 @@ require "couchrest/model/core_extensions/time_parsing"
# Base libraries # Base libraries
require "couchrest/model/casted_model" require "couchrest/model/casted_model"
require "couchrest/model/base" require "couchrest/model/base"
# Add rails support *after* everything has loaded
# Add rails support *after* everything has loaded
if defined?(Rails) if defined?(Rails)
require "couchrest/railtie" require "couchrest/railtie"
end end

View file

@ -1,40 +0,0 @@
require File.expand_path('../../spec_helper', __FILE__)
begin
require 'rubygems' unless ENV['SKIP_RUBYGEMS']
require 'active_support/json'
ActiveSupport::JSON.backend = :JSONGem
class PlainParent
class_inheritable_accessor :foo
self.foo = :bar
end
class PlainChild < PlainParent
end
class ExtendedParent < CouchRest::Model::Base
class_inheritable_accessor :foo
self.foo = :bar
end
class ExtendedChild < ExtendedParent
end
describe "Using chained inheritance without CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
PlainParent.foo.should == :bar
PlainChild.foo.should == :bar
end
end
describe "Using chained inheritance with CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
ExtendedParent.foo.should == :bar
ExtendedChild.foo.should == :bar
end
end
rescue LoadError
puts "This spec requires 'active_support/json' to be loaded"
end

View file

@ -22,6 +22,7 @@ class Article < CouchRest::Model::Base
property :date, Date property :date, Date
property :slug, :read_only => true property :slug, :read_only => true
property :user_id
property :title property :title
property :tags, [String] property :tags, [String]

View file

@ -83,18 +83,30 @@ class WithCallBacks < CouchRest::Model::Base
end end
end end
# Following two fixture classes have __intentionally__ diffent syntax for setting the validation context
class WithContextualValidationOnCreate < CouchRest::Model::Base
property(:name, String)
validates(:name, :presence => {:on => :create})
end
class WithContextualValidationOnUpdate < CouchRest::Model::Base
property(:name, String)
validates(:name, :presence => true, :on => :update)
end
class WithTemplateAndUniqueID < CouchRest::Model::Base class WithTemplateAndUniqueID < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
unique_id do |model| unique_id do |model|
model['important-field'] model.slug
end end
property :slug
property :preset, :default => 'value' property :preset, :default => 'value'
property :has_no_default property :has_no_default
end end
class WithGetterAndSetterMethods < CouchRest::Model::Base class WithGetterAndSetterMethods < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
property :other_arg property :other_arg
def arg def arg
other_arg other_arg
@ -107,7 +119,7 @@ end
class WithAfterInitializeMethod < CouchRest::Model::Base class WithAfterInitializeMethod < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
property :some_value property :some_value
def after_initialize def after_initialize

View file

@ -1,3 +1,5 @@
require 'person'
class Card < CouchRest::Model::Base class Card < CouchRest::Model::Base
# Set the default database to use # Set the default database to use
use_database DB use_database DB

View file

@ -17,3 +17,7 @@ class Cat < CouchRest::Model::Base
property :number property :number
end end
class ChildCat < Cat
property :mother, Cat
property :siblings, [Cat]
end

View file

@ -1,5 +1,5 @@
require File.join(FIXTURE_PATH, 'more', 'question') require 'question'
require File.join(FIXTURE_PATH, 'more', 'person') require 'person'
class Course < CouchRest::Model::Base class Course < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database

View file

@ -6,9 +6,9 @@ class Invoice < CouchRest::Model::Base
property :client_name property :client_name
property :employee_name property :employee_name
property :location property :location
# Validation # Validation
validates_presence_of :client_name, :employee_name validates_presence_of :client_name, :employee_name
validates_presence_of :location, :message => "Hey stupid!, you forgot the location" validates_presence_of :location, :message => "Hey stupid!, you forgot the location"
end end

5
spec/fixtures/models/key_chain.rb vendored Normal file
View file

@ -0,0 +1,5 @@
class KeyChain < CouchRest::Model::Base
use_database(DB)
property(:keys, Hash)
end

4
spec/fixtures/models/membership.rb vendored Normal file
View file

@ -0,0 +1,4 @@
class Membership < Hash
include CouchRest::Model::CastedModel
end

View file

@ -1,3 +1,5 @@
require 'cat'
class Person < Hash class Person < Hash
include ::CouchRest::Model::CastedModel include ::CouchRest::Model::CastedModel
property :pet, Cat property :pet, Cat

6
spec/fixtures/models/project.rb vendored Normal file
View file

@ -0,0 +1,6 @@
class Project < CouchRest::Model::Base
use_database DB
property :name, String
timestamps!
view_by :name
end

View file

@ -1,5 +1,6 @@
require File.join(FIXTURE_PATH, 'more', 'client') require 'client'
require File.join(FIXTURE_PATH, 'more', 'sale_entry') require 'sale_entry'
class SaleInvoice < CouchRest::Model::Base class SaleInvoice < CouchRest::Model::Base
use_database DB use_database DB
@ -10,4 +11,4 @@ class SaleInvoice < CouchRest::Model::Base
property :date, Date property :date, Date
property :price, Integer property :price, Integer
end end

View file

@ -0,0 +1,8 @@
require File.expand_path('../../spec_helper', __FILE__)
describe CouchRest::Model::Validations do
let(:invoice) do
Invoice.new()
end
end

View file

@ -1,11 +1,16 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require "bundler/setup" require "bundler/setup"
require "rubygems" require "rubygems"
require "rspec" # Satisfies Autotest and anyone else not using the Rake tasks require "rspec"
require File.join(File.dirname(__FILE__), '..','lib','couchrest_model') require 'couchrest_model'
# check the following file to see how to use the spec'd features.
unless defined?(FIXTURE_PATH) unless defined?(FIXTURE_PATH)
MODEL_PATH = File.join(File.dirname(__FILE__), "fixtures", "models")
$LOAD_PATH.unshift(MODEL_PATH)
FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures') FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures')
SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp') SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp')
@ -16,6 +21,21 @@ unless defined?(FIXTURE_PATH)
DB = TEST_SERVER.database(TESTDB) DB = TEST_SERVER.database(TESTDB)
end end
RSpec.configure do |config|
config.before(:all) { reset_test_db! }
config.after(:all) do
cr = TEST_SERVER
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
test_dbs.each do |db|
cr.database(db).delete! rescue nil
end
end
end
# Require each of the fixture models
Dir[ File.join(MODEL_PATH, "*.rb") ].sort.each { |file| require File.basename(file) }
class Basic < CouchRest::Model::Base class Basic < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
end end
@ -27,17 +47,6 @@ def reset_test_db!
DB DB
end end
RSpec.configure do |config|
config.before(:all) { reset_test_db! }
config.after(:all) do
cr = TEST_SERVER
test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
test_dbs.each do |db|
cr.database(db).delete! rescue nil
end
end
end
def couchdb_lucene_available? def couchdb_lucene_available?
lucene_path = "http://localhost:5985/" lucene_path = "http://localhost:5985/"

View file

@ -0,0 +1,30 @@
# encoding: utf-8
require 'spec_helper'
require 'test/unit/assertions'
require 'active_model/lint'
class CompliantModel < CouchRest::Model::Base
end
describe CouchRest::Model::Base do
include Test::Unit::Assertions
include ActiveModel::Lint::Tests
before :each do
@model = CompliantModel.new
end
describe "active model lint tests" do
ActiveModel::Lint::Tests.public_instance_methods.map{|m| m.to_s}.grep(/^test/).each do |m|
example m.gsub('_',' ') do
send m
end
end
end
def model
@model
end
end

View file

@ -1,7 +1,5 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
require File.join(FIXTURE_PATH, 'more', 'sale_invoice')
describe "Assocations" do describe "Assocations" do

View file

@ -1,4 +1,4 @@
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
describe "Model attachments" do describe "Model attachments" do

View file

@ -1,11 +1,5 @@
# encoding: utf-8 # encoding: utf-8
require "spec_helper"
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'base')
describe "Model Base" do describe "Model Base" do
@ -49,8 +43,34 @@ describe "Model Base" do
@obj.database.should eql('database') @obj.database.should eql('database')
end end
it "should only set defined properties" do
@doc = WithDefaultValues.new(:name => 'test', :foo => 'bar')
@doc['name'].should eql('test')
@doc['foo'].should be_nil
end
it "should set all properties with :directly_set_attributes option" do
@doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true)
@doc['name'].should eql('test')
@doc['foo'].should eql('bar')
end
it "should set the model type" do
@doc = WithDefaultValues.new()
@doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues')
end
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
it "should call after_initialize after block" do
@doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"}
@doc['some_value'].should eql('foo')
end
end end
describe "ActiveModel compatability Basic" do describe "ActiveModel compatability Basic" do
before(:each) do before(:each) do
@ -90,14 +110,22 @@ describe "Model Base" do
describe "#persisted?" do describe "#persisted?" do
context "when the document is new" do context "when the document is new" do
it "returns false" do it "returns false" do
@obj.persisted?.should == false @obj.persisted?.should be_false
end end
end end
context "when the document is not new" do context "when the document is not new" do
it "returns id" do it "returns id" do
@obj.save @obj.save
@obj.persisted?.should == true @obj.persisted?.should be_true
end
end
context "when the document is destroyed" do
it "returns false" do
@obj.save
@obj.destroy
@obj.persisted?.should be_false
end end
end end
end end
@ -109,9 +137,40 @@ describe "Model Base" do
end end
end end
describe "#destroyed?" do
it "should be present" do
@obj.should respond_to(:destroyed?)
end
it "should return false with new object" do
@obj.destroyed?.should be_false
end
it "should return true after destroy" do
@obj.save
@obj.destroy
@obj.destroyed?.should be_true
end
end
end end
describe "comparisons" do
describe "#==" do
it "should be true on same document" do
p = Project.create
p.should eql(p)
end
it "should be true after loading" do
p = Project.create
p.should eql(Project.get(p.id))
end
it "should not be true if databases do not match" do
p = Project.create
p2 = p.dup
p2.stub!(:database).and_return('other')
p.should_not eql(p2)
end
end
end
describe "update attributes without saving" do describe "update attributes without saving" do
before(:each) do before(:each) do
a = Article.get "big-bad-danger" rescue nil a = Article.get "big-bad-danger" rescue nil
@ -152,7 +211,7 @@ describe "Model Base" do
}.should_not raise_error }.should_not raise_error
@art.slug.should == "big-bad-danger" @art.slug.should == "big-bad-danger"
end end
#it "should not change other attributes if there is an error" do #it "should not change other attributes if there is an error" do
# lambda { # lambda {
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger") # @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
@ -160,7 +219,7 @@ describe "Model Base" do
# @art['title'].should == "big bad danger" # @art['title'].should == "big bad danger"
#end #end
end end
describe "update attributes" do describe "update attributes" do
before(:each) do before(:each) do
a = Article.get "big-bad-danger" rescue nil a = Article.get "big-bad-danger" rescue nil
@ -175,7 +234,7 @@ describe "Model Base" do
loaded['title'].should == "super danger" loaded['title'].should == "super danger"
end end
end end
describe "with default" do describe "with default" do
it "should have the default value set at initalization" do it "should have the default value set at initalization" do
@obj.preset.should == {:right => 10, :top_align => false} @obj.preset.should == {:right => 10, :top_align => false}
@ -232,7 +291,7 @@ describe "Model Base" do
WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete WithTemplateAndUniqueID.database.bulk_delete
@tmpl = WithTemplateAndUniqueID.new @tmpl = WithTemplateAndUniqueID.new
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1') @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
end end
it "should have fields set when new" do it "should have fields set when new" do
@tmpl.preset.should == 'value' @tmpl.preset.should == 'value'
@ -253,10 +312,10 @@ describe "Model Base" do
before(:all) do before(:all) do
WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete WithTemplateAndUniqueID.database.bulk_delete
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save WithTemplateAndUniqueID.new('slug' => '4').save
end end
it "should find all" do it "should find all" do
rs = WithTemplateAndUniqueID.all rs = WithTemplateAndUniqueID.all
@ -274,9 +333,9 @@ describe "Model Base" do
end end
it ".count should return the number of documents" do it ".count should return the number of documents" do
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.count.should == 3 WithTemplateAndUniqueID.count.should == 3
end end
@ -285,14 +344,14 @@ describe "Model Base" do
describe "finding the first instance of a model" do describe "finding the first instance of a model" do
before(:each) do before(:each) do
@db = reset_test_db! @db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save WithTemplateAndUniqueID.new('slug' => '4').save
end end
it "should find first" do it "should find first" do
rs = WithTemplateAndUniqueID.first rs = WithTemplateAndUniqueID.first
rs['important-field'].should == "1" rs['slug'].should == "1"
end end
it "should return nil if no instances are found" do it "should return nil if no instances are found" do
WithTemplateAndUniqueID.all.each {|obj| obj.destroy } WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
@ -370,14 +429,7 @@ describe "Model Base" do
end end
end end
describe "initialization" do describe "recursive validation on a model" do
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
end
describe "recursive validation on a model" do
before :each do before :each do
reset_test_db! reset_test_db!
@cat = Cat.new(:name => 'Sockington') @cat = Cat.new(:name => 'Sockington')

View file

@ -1,12 +1,5 @@
# encoding: utf-8 # encoding: utf-8
require "spec_helper"
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'question')
require File.join(FIXTURE_PATH, 'more', 'course')
class WithCastedModelMixin < Hash class WithCastedModelMixin < Hash
include CouchRest::Model::CastedModel include CouchRest::Model::CastedModel

View file

@ -1,7 +1,4 @@
require File.expand_path('../../spec_helper', __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
class Driver < CouchRest::Model::Base class Driver < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database

View file

@ -1,4 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
class UnattachedDoc < CouchRest::Model::Base class UnattachedDoc < CouchRest::Model::Base
# Note: no use_database here # Note: no use_database here
@ -123,6 +123,35 @@ describe "Proxy Class" do
u.respond_to?(:database).should be_false u.respond_to?(:database).should be_false
end end
end end
describe "#get!" do
it "raises exception when passed a nil" do
expect { @us.get!(nil)}.to raise_error(CouchRest::Model::DocumentNotFound)
end
it "raises exception when passed an empty string " do
expect { @us.get!("")}.to raise_error(CouchRest::Model::DocumentNotFound)
end
it "raises exception when document with provided id does not exist" do
expect { @us.get!("thisisnotreallyadocumentid")}.to raise_error(CouchRest::Model::DocumentNotFound)
end
end
describe "#find!" do
it "raises exception when passed a nil" do
expect { @us.find!(nil)}.to raise_error(CouchRest::Model::DocumentNotFound)
end
it "raises exception when passed an empty string " do
expect { @us.find!("")}.to raise_error(CouchRest::Model::DocumentNotFound)
end
it "raises exception when document with provided id does not exist" do
expect { @us.find!("thisisnotreallyadocumentid")}.to raise_error(CouchRest::Model::DocumentNotFound)
end
end
# Sam Lown 2010-04-07 # Sam Lown 2010-04-07
# Removed as unclear why this should happen as before my changes # Removed as unclear why this should happen as before my changes
# this happend by accident, not explicitly. # this happend by accident, not explicitly.

View file

@ -1,5 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'article')
describe "Collections" do describe "Collections" do
@ -27,21 +26,20 @@ describe "Collections" do
end end
it "should provide a class method for paginate" do it "should provide a class method for paginate" do
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
:per_page => 3, :descending => true, :key => Date.today, :include_docs => true) :per_page => 3, :descending => true, :key => Date.today)
articles.size.should == 3 articles.size.should == 3
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
:per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true) :per_page => 3, :page => 2, :descending => true, :key => Date.today)
articles.size.should == 3 articles.size.should == 3
articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date', articles = Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
:per_page => 3, :page => 3, :descending => true, :key => Date.today, :include_docs => true) :per_page => 3, :page => 3, :descending => true, :key => Date.today)
articles.size.should == 1 articles.size.should == 1
end end
it "should provide a class method for paginated_each" do it "should provide a class method for paginated_each" do
options = { :design_doc => 'Article', :view_name => 'by_date', options = { :design_doc => 'Article', :view_name => 'by_date',
:per_page => 3, :page => 1, :descending => true, :key => Date.today, :per_page => 3, :page => 1, :descending => true, :key => Date.today }
:include_docs => true }
Article.paginated_each(options) do |a| Article.paginated_each(options) do |a|
a.should_not be_nil a.should_not be_nil
end end

View file

@ -1,8 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
describe CouchRest::Model::Base do describe CouchRest::Model::Configuration do
before do before do
@class = Class.new(CouchRest::Model::Base) @class = Class.new(CouchRest::Model::Base)

View file

@ -1,7 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
describe CouchRest::Model::Base do describe CouchRest::Model::Connection do
before do before do
@class = Class.new(CouchRest::Model::Base) @class = Class.new(CouchRest::Model::Base)

View file

@ -1,10 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require 'spec_helper'
require File.expand_path("../../spec_helper", __FILE__) describe CouchRest::Model::DesignDoc do
require File.join(FIXTURE_PATH, 'base')
require File.join(FIXTURE_PATH, 'more', 'article')
describe "Design Documents" do
before :all do before :all do
reset_test_db! reset_test_db!
@ -202,7 +199,7 @@ describe "Design Documents" do
describe "lazily refreshing the design document" do describe "lazily refreshing the design document" do
before(:all) do before(:all) do
@db = reset_test_db! @db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
end end
it "should not save the design doc twice" do it "should not save the design doc twice" do
WithTemplateAndUniqueID.all WithTemplateAndUniqueID.all

View file

@ -1,10 +1,9 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
class DesignModel < CouchRest::Model::Base class DesignModel < CouchRest::Model::Base
end end
describe "Design" do describe CouchRest::Model::Designs do
it "should accessable from model" do it "should accessable from model" do
DesignModel.respond_to?(:design).should be_true DesignModel.respond_to?(:design).should be_true

View file

@ -1,10 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'base')
class WithCastedModelMixin < Hash class WithCastedModelMixin < Hash
include CouchRest::Model::CastedModel include CouchRest::Model::CastedModel
@ -241,6 +235,14 @@ describe "Dirty" do
end end
end end
it "should report changes if an array is popped after reload" do
should_change_array do |array, obj|
obj.reload
obj.keywords.pop
end
end
it "should report no changes if an empty array is popped" do it "should report no changes if an empty array is popped" do
should_not_change_array do |array, obj| should_not_change_array do |array, obj|
array.clear array.clear
@ -249,6 +251,50 @@ describe "Dirty" do
end end
end end
it "should report changes on deletion from an array" do
should_change_array do |array, obj|
array << "keyword"
obj.save!
array.delete_at(0)
end
should_change_array do |array, obj|
array << "keyword"
obj.save!
array.delete("keyword")
end
end
it "should report changes on deletion from an array after reload" do
should_change_array do |array, obj|
array << "keyword"
obj.save!
obj.reload
array.delete_at(0)
end
should_change_array do |array, obj|
array << "keyword"
obj.save!
obj.reload
array.delete("keyword")
end
end
it "should report no changes on deletion from an empty array" do
should_not_change_array do |array, obj|
array.clear
obj.save!
array.delete_at(0)
end
should_not_change_array do |array, obj|
array.clear
obj.save!
array.delete("keyword")
end
end
it "should report changes if an array is pushed" do it "should report changes if an array is pushed" do
should_change_array do |array, obj| should_change_array do |array, obj|
array.push("keyword") array.push("keyword")

View file

@ -0,0 +1,33 @@
require 'spec_helper'
class PlainParent
class_inheritable_accessor :foo
self.foo = :bar
end
class PlainChild < PlainParent
end
class ExtendedParent < CouchRest::Model::Base
class_inheritable_accessor :foo
self.foo = :bar
end
class ExtendedChild < ExtendedParent
end
describe "Using chained inheritance without CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
PlainParent.foo.should == :bar
PlainChild.foo.should == :bar
end
end
describe "Using chained inheritance with CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
ExtendedParent.foo.should == :bar
ExtendedChild.foo.should == :bar
end
end

View file

@ -1,12 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
require File.join(FIXTURE_PATH, 'base')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
describe "Model Persistence" do describe CouchRest::Model::Persistence do
before(:each) do before(:each) do
@obj = WithDefaultValues.new @obj = WithDefaultValues.new
@ -34,11 +29,11 @@ describe "Model Persistence" do
describe "basic saving and retrieving" do describe "basic saving and retrieving" do
it "should work fine" do it "should work fine" do
@obj.name = "should be easily saved and retrieved" @obj.name = "should be easily saved and retrieved"
@obj.save @obj.save!
saved_obj = WithDefaultValues.get(@obj.id) saved_obj = WithDefaultValues.get!(@obj.id)
saved_obj.should_not be_nil saved_obj.should_not be_nil
end end
it "should parse the Time attributes automatically" do it "should parse the Time attributes automatically" do
@obj.name = "should parse the Time attributes automatically" @obj.name = "should parse the Time attributes automatically"
@obj.set_by_proc.should be_an_instance_of(Time) @obj.set_by_proc.should be_an_instance_of(Time)
@ -81,6 +76,18 @@ describe "Model Persistence" do
article.should_not be_new article.should_not be_new
end end
it "yields new instance to block before saving (#create)" do
article = Article.create{|a| a.title = 'my create init block test'}
article.title.should == 'my create init block test'
article.should_not be_new
end
it "yields new instance to block before saving (#create!)" do
article = Article.create{|a| a.title = 'my create bang init block test'}
article.title.should == 'my create bang init block test'
article.should_not be_new
end
it "should trigger the create callbacks" do it "should trigger the create callbacks" do
doc = WithCallBacks.create(:name => 'my other test') doc = WithCallBacks.create(:name => 'my other test')
doc.run_before_create.should be_true doc.run_before_create.should be_true
@ -210,57 +217,66 @@ describe "Model Persistence" do
it "should require the field" do it "should require the field" do
lambda{@templated.save}.should raise_error lambda{@templated.save}.should raise_error
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
end end
it "should save with the id" do it "should save with the id" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important') t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated t.should == @templated
end end
it "should not change the id on update" do it "should not change the id on update" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
@templated['important-field'] = 'not-important' @templated['slug'] = 'not-important'
@templated.save.should be_true @templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important') t = WithTemplateAndUniqueID.get('very-important')
t.id.should == @templated.id t.id.should == @templated.id
end end
it "should raise an error when the id is taken" do it "should raise an error when the id is taken" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error lambda{WithTemplateAndUniqueID.new('slug' => 'very-important').save}.should raise_error
end end
it "should set the id" do it "should set the id" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
@templated.id.should == 'very-important' @templated.id.should == 'very-important'
end end
end end
describe "destroying an instance" do describe "destroying an instance" do
before(:each) do before(:each) do
@dobj = Basic.new @dobj = Event.new
@dobj.save.should be_true @dobj.save.should be_true
end end
it "should return true" do it "should return true" do
result = @dobj.destroy result = @dobj.destroy
result.should be_true result.should be_true
end end
it "should be resavable" do
@dobj.destroy
@dobj.rev.should be_nil
@dobj.id.should be_nil
@dobj.save.should be_true
end
it "should make it go away" do it "should make it go away" do
@dobj.destroy @dobj.destroy
lambda{Basic.get!(@dobj.id)}.should raise_error lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound)
end
it "should freeze the object" do
@dobj.destroy
# In Ruby 1.9.2 this raises RuntimeError, in 1.8.7 TypeError, D'OH!
lambda { @dobj.subject = "Test" }.should raise_error(StandardError)
end
it "trying to save after should fail" do
@dobj.destroy
lambda { @dobj.save }.should raise_error(StandardError)
lambda{Basic.get!(@dobj.id)}.should raise_error(CouchRest::Model::DocumentNotFound)
end
it "should make destroyed? true" do
@dobj.destroyed?.should be_false
@dobj.destroy
@dobj.destroyed?.should be_true
end end
end end
@ -340,6 +356,27 @@ describe "Model Persistence" do
end end
end end
describe "with contextual validation on ”create”" do
it "should validate only within ”create” context" do
doc = WithContextualValidationOnCreate.new
doc.save.should be_false
doc.name = "Alice"
doc.save.should be_true
doc.update_attributes(:name => nil).should be_true
end
end
describe "with contextual validation on ”update”" do
it "should validate only within ”update” context" do
doc = WithContextualValidationOnUpdate.new
doc.save.should be_true
doc.update_attributes(:name => nil).should be_false
doc.update_attributes(:name => "Bob").should be_true
end
end
describe "save" do describe "save" do
it "should run the after filter after saving" do it "should run the after filter after saving" do
@doc.run_after_save.should be_nil @doc.run_after_save.should be_nil

View file

@ -1,4 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
describe "Model Attributes" do describe "Model Attributes" do

View file

@ -1,17 +1,7 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'invoice')
require File.join(FIXTURE_PATH, 'more', 'service')
require File.join(FIXTURE_PATH, 'more', 'event')
require File.join(FIXTURE_PATH, 'more', 'user')
require File.join(FIXTURE_PATH, 'more', 'course')
describe CouchRest::Model::Property do
describe "Model properties" do
before(:each) do before(:each) do
reset_test_db! reset_test_db!
@ -239,6 +229,16 @@ describe "Model properties" do
end end
describe "properties of hash of casted models" do
it "should be able to assign a casted hash to a hash property" do
chain = KeyChain.new
keys = {"House" => "8==$", "Office" => "<>==U"}
chain.keys = keys
chain.keys = chain.keys
chain.keys.should == keys
end
end
describe "properties of array of casted models" do describe "properties of array of casted models" do
before(:each) do before(:each) do
@ -265,9 +265,9 @@ describe "properties of array of casted models" do
end end
it "should allow attribute to be set from hash with ordered keys and sub-hashes" do it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
@course.questions = { '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} } @course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
@course.questions.length.should eql(2) @course.questions.length.should eql(3)
@course.questions.last.q.should eql('Test2') @course.questions.last.q.should eql('Test10')
@course.questions.last.class.should eql(Question) @course.questions.last.class.should eql(Question)
end end
@ -284,7 +284,7 @@ describe "properties of array of casted models" do
it "should raise an error if attempting to set single value for array type" do it "should raise an error if attempting to set single value for array type" do
lambda { lambda {
@course.questions = Question.new(:q => 'test1') @course.questions = Question.new(:q => 'test1')
}.should raise_error }.should raise_error(/Expecting an array/)
end end
@ -315,6 +315,28 @@ describe "a casted model retrieved from the database" do
end end
end end
describe "nested models (not casted)" do
before(:each) do
reset_test_db!
@cat = ChildCat.new(:name => 'Stimpy')
@cat.mother = {:name => 'Stinky'}
@cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
@cat.save
@cat = ChildCat.get(@cat.id)
end
it "should correctly save single relation" do
@cat.mother.name.should eql('Stinky')
@cat.mother.casted_by.should eql(@cat)
end
it "should correctly save collection" do
@cat.siblings.first.name.should eql("Feather")
@cat.siblings.last.casted_by.should eql(@cat)
end
end
describe "Property Class" do describe "Property Class" do
it "should provide name as string" do it "should provide name as string" do

View file

@ -1,6 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
class DummyProxyable < CouchRest::Model::Base class DummyProxyable < CouchRest::Model::Base
proxy_database_method :db proxy_database_method :db
@ -12,7 +10,7 @@ end
class ProxyKitten < CouchRest::Model::Base class ProxyKitten < CouchRest::Model::Base
end end
describe "Proxyable" do describe CouchRest::Model::Proxyable do
describe "#proxy_database" do describe "#proxy_database" do
@ -87,7 +85,7 @@ describe "Proxyable" do
DummyProxyable.proxy_for(:cats) DummyProxyable.proxy_for(:cats)
@obj = DummyProxyable.new @obj = DummyProxyable.new
CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true) CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true)
@obj.should_receive('proxy_database').and_return('db') @obj.should_receive(:proxy_database).and_return('db')
@obj.cats @obj.cats
end end
@ -165,15 +163,13 @@ describe "Proxyable" do
end end
it "should proxy new call" do it "should proxy new call" do
Cat.should_receive(:new).and_return({}) @obj.should_receive(:proxy_block_update).with(:new, 'attrs', 'opts')
@obj.should_receive(:proxy_update).and_return(true) @obj.new('attrs', 'opts')
@obj.new
end end
it "should proxy build_from_database" do it "should proxy build_from_database" do
Cat.should_receive(:build_from_database).and_return({}) @obj.should_receive(:proxy_block_update).with(:build_from_database, 'attrs', 'opts')
@obj.should_receive(:proxy_update).with({}).and_return(true) @obj.build_from_database('attrs', 'opts')
@obj.build_from_database
end end
describe "#method_missing" do describe "#method_missing" do
@ -313,6 +309,15 @@ describe "Proxyable" do
@obj.send(:proxy_update_all, docs) @obj.send(:proxy_update_all, docs)
end end
describe "#proxy_block_update" do
it "should proxy block updates" do
doc = { }
@obj.model.should_receive(:new).and_yield(doc)
@obj.should_receive(:proxy_update).with(doc)
@obj.send(:proxy_block_update, :new)
end
end
end end
end end

View file

@ -1,8 +1,4 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'more', 'course')
# add a default value # add a default value
Card.property :bg_color, :default => '#ccc' Card.property :bg_color, :default => '#ccc'

View file

@ -1,8 +1,5 @@
# encoding: utf-8 # encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__) require 'spec_helper'
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "Type Casting" do describe "Type Casting" do

View file

@ -1,14 +1,6 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat') describe CouchRest::Model::Validations do
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'base')
# TODO Move validations from other specs to here
describe "Validations" do
describe "Uniqueness" do describe "Uniqueness" do

View file

@ -1,10 +1,6 @@
require File.expand_path("../../spec_helper", __FILE__) require "spec_helper"
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
describe "Model views" do describe CouchRest::Model::Views do
class Unattached < CouchRest::Model::Base class Unattached < CouchRest::Model::Base
property :title property :title
@ -17,7 +13,6 @@ describe "Model views" do
nil nil
end end
end end
describe "ClassMethods" do describe "ClassMethods" do
# NOTE! Add more unit tests! # NOTE! Add more unit tests!