Merge remote branch 'refs/remotes/canonical/master' into design-mapper-more-auto-update-design-doc-aware

This commit is contained in:
Peter Williams 2011-11-29 08:22:30 -07:00
commit 88bb413ec2
66 changed files with 866 additions and 423 deletions

View file

@ -70,6 +70,10 @@ The example config above for example would use a database called "project_test".
## Generators
### Configuration
$ rails generate couchrest_model:config
### Model
$ rails generate model person --orm=couchrest_model

View file

@ -1 +1 @@
1.1.0.beta5
1.1.2

View file

@ -6,7 +6,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
s.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
s.date = %q{2011-04-29}
s.date = File.mtime('VERSION')
s.description = %q{CouchRest Model provides aditional features to the standard CouchRest Document class such as properties, view designs, associations, callbacks, typecasting and validations.}
s.email = %q{jchris@apache.org}
s.extra_rdoc_files = [
@ -23,12 +23,14 @@ Gem::Specification.new do |s|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_dependency(%q<couchrest>, "1.1.0.pre2")
s.add_dependency(%q<couchrest>, "~> 1.1.2")
s.add_dependency(%q<mime-types>, "~> 1.15")
s.add_dependency(%q<activemodel>, "~> 3.0")
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("rake", ">= 0.8.0")
# s.add_development_dependency("jruby-openssl", ">= 0.7.3")
end

View file

@ -1,10 +1,41 @@
# CouchRest Model Change History
## 1.1.0 - 2011-05-XX
## 1.1.3
* CouchRest::Model::Base.respond_to_missing? and respond_to? (Kim Burgestrand)
## 1.1.2 - 2011-07-23
* Minor fixes
* Upgrade to couchrest 1.1.2
* Override as_couch_json to ensure nil values not stored
* Removing restriction that prohibited objects that cast as an array to be loaded.
## 1.1.1 - 2011-07-04
* Minor fix
* Bumping CouchRest version dependency for important initialize method fix.
* Ensuring super on Embeddable#initialize can be called.
## 1.1.0 - 2011-06-25
* Major Alterations
* CastedModel no longer requires a Hash. Automatically includes all required methods.
* CastedModel module renamed to Embeddable (old still works!)
* Minor Fixes
* Validation callbacks now support context (thanks kostia)
* Document comparisons now performed using database and document ID (pointer by neocsr)
* Automatic config generation now supported (thanks lucasrenan)
* Comparing documents resorts to Hash comparison if both IDs are nil. (pointer by kostia)
## 1.1.0.rc1 - 2011-06-08
* New Features
* Properties with a nil value are now no longer sent to the database.
* 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
* #as_json now correctly uses ActiveSupports methods.
@ -14,7 +45,12 @@
* DesignDoc cache refreshed if a database is deleted.
* Fixing dirty tracking on collection_of association.
* 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

View file

@ -1,13 +1,12 @@
module CouchRest
module Model
class Base < Document
class Base < CouchRest::Document
extend ActiveModel::Naming
include CouchRest::Model::Configuration
include CouchRest::Model::Connection
include CouchRest::Model::Persistence
include CouchRest::Model::Callbacks
include CouchRest::Model::DocumentQueries
include CouchRest::Model::Views
include CouchRest::Model::DesignDoc
@ -18,9 +17,11 @@ module CouchRest
include CouchRest::Model::PropertyProtection
include CouchRest::Model::Associations
include CouchRest::Model::Validations
include CouchRest::Model::Callbacks
include CouchRest::Model::Designs
include CouchRest::Model::CastedBy
include CouchRest::Model::Dirty
include CouchRest::Model::Callbacks
def self.subclasses
@subclasses ||= []
@ -46,22 +47,24 @@ module CouchRest
#
# Options supported:
#
# * :directly_set_attributes: true when data comes directly from database
# * :database: provide an alternative database
# * :directly_set_attributes, true when data comes directly from database
# * :database, provide an alternative database
#
# If a block is provided the new model will be passed into the
# block so that it can be populated.
def initialize(doc = {}, options = {})
doc = prepare_all_attributes(doc, options)
# set the instances database, if provided
def initialize(attributes = {}, options = {})
super()
prepare_all_attributes(attributes, options)
# set the instance's database, if provided
self.database = options[:database] unless options[:database].nil?
super(doc)
unless self['_id'] && self['_rev']
self[self.model_type_key] = self.class.to_s
end
yield self if block_given?
after_initialize if respond_to?(:after_initialize)
run_callbacks(:initialize) { self }
end
@ -79,18 +82,19 @@ module CouchRest
super
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
# compatbility for 1.8, it does not use respond_to_missing?
# thing is, when using it like this only, doing method(:find_by_view)
# will throw an error
def self.respond_to?(m, include_private = false)
super || respond_to_missing?(m, include_private)
end
alias :kind_of? :is_a?
def persisted?
!new?
# ruby 1.9 feature
# this allows ruby to know that the method is defined using
# method_missing, and as such, method(:find_by_view) will actually
# give a Method back, and not throw an error like in 1.8!
def self.respond_to_missing?(m, include_private = false)
has_view?(m) || has_view?(m.to_s[/^find_(by_.+)/, 1])
end
def to_key
@ -100,6 +104,26 @@ module CouchRest
alias :to_param :id
alias :new_record? :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)
if id.nil? && other.id.nil?
# no ids? assume comparing nested and revert to hash comparison
to_hash == other.to_hash
else
database == other.database && id == other.id
end
end
alias :eql? :==
end
end
end

View file

@ -5,21 +5,23 @@ module CouchRest #:nodoc:
module Callbacks
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
extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
define_model_callbacks \
:create,
:destroy,
:save,
:update
define_model_callbacks :initialize, :only => :after
define_model_callbacks :create, :destroy, :save, :update
end
def valid?(*) #nodoc
_run_validation_callbacks { super }
end
end
end

View file

@ -50,6 +50,16 @@ module CouchRest::Model
super
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)
obj = casted_by_property.build(*args)
self.push(obj)

View file

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

View file

@ -1,10 +1,7 @@
module CouchRest
module Model
module DocumentQueries
def self.included(base)
base.extend(ClassMethods)
end
extend ActiveSupport::Concern
module ClassMethods

View file

@ -1,37 +1,37 @@
module CouchRest::Model
module CastedModel
module Embeddable
extend ActiveSupport::Concern
# Include Attributes early to ensure super() will work
include CouchRest::Attributes
included do
include CouchRest::Model::Configuration
include CouchRest::Model::Callbacks
include CouchRest::Model::Properties
include CouchRest::Model::PropertyProtection
include CouchRest::Model::Associations
include CouchRest::Model::Validations
include CouchRest::Model::Callbacks
include CouchRest::Model::CastedBy
include CouchRest::Model::Dirty
include CouchRest::Model::Callbacks
class_eval do
# Override CastedBy's base_doc?
def base_doc?
false # Can never be base doc!
end
end
end
def initialize(keys = {})
raise StandardError unless self.is_a? Hash
prepare_all_attributes(keys)
# Initialize a new Casted Model. Accepts the same
# options as CouchRest::Model::Base for preparing and initializing
# attributes.
def initialize(keys = {}, options = {})
super()
end
def []= key, value
super(key.to_s, value)
end
def [] key
super(key.to_s)
prepare_all_attributes(keys, options)
run_callbacks(:initialize) { self }
end
# False if the casted model has already
@ -65,6 +65,14 @@ module CouchRest::Model
end
alias :attributes= :update_attributes_without_saving
end # End Embeddable
# Provide backwards compatability with previous versions (pre 1.1.0)
module CastedModel
extend ActiveSupport::Concern
included do
include CouchRest::Model::Embeddable
end
end
end

View file

@ -21,14 +21,15 @@ module CouchRest
# 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
def create!(options = {})
self.class.fail_validate!(self) unless self.create(options)
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?
raise "Cannot save a destroyed document!" if destroyed?
raise "Calling #{self.class.name}#update on document that has not been created!" if new?
return false unless perform_validations(options)
return true if !self.disable_dirty && !self.changed?
_run_update_callbacks do
@ -54,19 +55,25 @@ module CouchRest
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')
@_destroyed = true
self.freeze
end
result['ok']
end
end
def destroyed?
!!@_destroyed
end
def persisted?
!new? && !destroyed?
end
# Update the document's attributes and save. For example:
#
# doc.update_attributes :name => "Fred"
@ -85,7 +92,7 @@ module CouchRest
#
# Returns self.
def reload
merge!(self.class.get(id))
prepare_all_attributes(database.get(id), :directly_set_attributes => true)
self
end
@ -104,22 +111,25 @@ module CouchRest
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
# a document instance
#
def build_from_database(doc = {})
base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize
base.new(doc, :directly_set_attributes => true)
def build_from_database(doc = {}, options = {}, &block)
src = doc[model_type_key]
base = (src.blank? || src == self.to_s) ? self : src.constantize
base.new(doc, options.merge(:directly_set_attributes => true), &block)
end
# Defines an instance and save it directly to the database
#
# ==== Returns
# returns the reloaded document
def create(attributes = {})
instance = new(attributes)
def create(attributes = {}, &block)
instance = new(attributes, &block)
instance.create
instance
end
@ -128,8 +138,8 @@ module CouchRest
#
# ==== Returns
# returns the reloaded document or raises an exception
def create!(attributes = {})
instance = new(attributes)
def create!(attributes = {}, &block)
instance = new(attributes, &block)
instance.create!
instance
end

View file

@ -12,8 +12,10 @@ module CouchRest
raise "You can only mixin Properties in a class responding to [] and []=, if you tried to mixin CastedModel, make sure your class inherits from Hash or responds to the proper methods" unless (method_defined?(:[]) && method_defined?(:[]=))
end
def as_json(options = nil)
Hash[self].reject{|k,v| v.nil?}.as_json(options)
# Provide an attribute hash ready to be sent to CouchDB but with
# all the nil attributes removed.
def as_couch_json
super.delete_if{|k,v| v.nil?}
end
# Returns the Class properties with their values
@ -80,17 +82,18 @@ module CouchRest
self.disable_dirty = dirty
end
def prepare_all_attributes(doc = {}, options = {})
def prepare_all_attributes(attrs = {}, options = {})
self.disable_dirty = !!options[:directly_set_attributes]
apply_all_property_defaults
if options[:directly_set_attributes]
directly_set_read_only_attributes(doc)
directly_set_read_only_attributes(attrs)
directly_set_attributes(attrs, true)
else
doc = remove_protected_attributes(doc)
attrs = remove_protected_attributes(attrs)
directly_set_attributes(attrs)
end
res = doc.nil? ? doc : directly_set_attributes(doc)
self.disable_dirty = false
res
self
end
def find_property!(property)
@ -101,16 +104,13 @@ module CouchRest
# Set all the attributes and return a hash with the attributes
# that have not been accepted.
def directly_set_attributes(hash)
hash.reject do |attribute_name, attribute_value|
if self.respond_to?("#{attribute_name}=")
self.send("#{attribute_name}=", attribute_value)
true
elsif mass_assign_any_attribute # config option
self[attribute_name] = attribute_value
true
else
false
def directly_set_attributes(hash, mass_assign = false)
return if hash.nil?
hash.reject do |key, value|
if self.respond_to?("#{key}=")
self.send("#{key}=", value)
elsif mass_assign || mass_assign_any_attribute
self[key] = value
end
end
end
@ -151,15 +151,13 @@ module CouchRest
# These properties are casted as Time objects, so they should always
# be set to UTC.
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)
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
set_callback :save, :before do |object|
write_attribute('updated_at', Time.now)
write_attribute('created_at', Time.now) if object.new?
end
end
protected
@ -170,8 +168,8 @@ module CouchRest
# check if this property is going to casted
type = options.delete(:type) || options.delete(:cast_as)
if block_given?
type = Class.new(Hash) do
include CastedModel
type = Class.new do
include Embeddable
end
if block.arity == 1 # Traditional, with options
type.class_eval { yield type }
@ -193,42 +191,32 @@ module CouchRest
# defines the getter for the property (and optional aliases)
def create_property_getter(property)
# meth = property.name
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{property.name}
read_attribute('#{property.name}')
end
EOS
define_method(property.name) do
read_attribute(property.name)
end
if ['boolean', TrueClass.to_s.downcase].include?(property.type.to_s.downcase)
class_eval <<-EOS, __FILE__, __LINE__
def #{property.name}?
value = read_attribute('#{property.name}')
!(value.nil? || value == false)
end
EOS
define_method("#{property.name}?") do
value = read_attribute(property.name)
!(value.nil? || value == false)
end
end
if property.alias
class_eval <<-EOS, __FILE__, __LINE__ + 1
alias #{property.alias.to_sym} #{property.name.to_sym}
EOS
alias_method(property.alias, property.name.to_sym)
end
end
# defines the setter for the property (and optional aliases)
def create_property_setter(property)
property_name = property.name
class_eval <<-EOS
def #{property_name}=(value)
write_attribute('#{property_name}', value)
end
EOS
name = property.name
define_method("#{name}=") do |value|
write_attribute(name, value)
end
if property.alias
class_eval <<-EOS
alias #{property.alias.to_sym}= #{property_name.to_sym}=
EOS
alias_method "#{property.alias}=", "#{name}="
end
end

View file

@ -27,19 +27,15 @@ module CouchRest::Model
if value.nil?
value = []
elsif [Hash, HashWithIndifferentAccess].include?(value.class)
# Assume provided as a Hash where key is index!
data = value
value = [ ]
data.keys.sort.each do |k|
value << data[k]
end
# Assume provided as a params hash where key is index
value = parameter_hash_to_array(value)
elsif !value.is_a?(Array)
raise "Expecting an array or keyed hash for property #{parent.class.name}##{self.name}"
end
arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray
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
CastedHash[value, self, parent]
elsif !value.nil?
@ -47,9 +43,8 @@ module CouchRest::Model
end
end
# Cast an individual value, not an array
# Cast an individual value
def cast_value(parent, value)
raise "An array inside an array cannot be casted, use CastedModel" if value.is_a?(Array)
value = typecast_value(value, self)
associate_casted_value_to_parent(parent, value)
end
@ -78,6 +73,14 @@ module CouchRest::Model
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)
value.casted_by = parent if value.respond_to?(:casted_by)
value.casted_by_property = self if value.respond_to?(:casted_by_property)

View file

@ -73,12 +73,12 @@ module CouchRest
end
# Base
def new(*args)
proxy_update(model.new(*args))
def new(attrs = {}, options = {}, &block)
proxy_block_update(:new, attrs, options, &block)
end
def build_from_database(doc = {})
proxy_update(model.build_from_database(doc))
def build_from_database(attrs = {}, options = {}, &block)
proxy_block_update(:build_from_database, attrs, options, &block)
end
def method_missing(m, *args, &block)
@ -170,6 +170,13 @@ module CouchRest
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

View file

@ -18,7 +18,7 @@ CouchRest::Design.class_eval do
flatten =
lambda {|r|
(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
elsif v.is_a?(Array)
v.flatten.map{|v| recurse.call(v)}

View file

@ -13,18 +13,29 @@ module CouchRest
# Validations may be applied to both Model::Base and Model::CastedModel
module Validations
extend ActiveSupport::Concern
included do
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
end
include ActiveModel::Validations
# 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
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

View file

@ -2,10 +2,13 @@ require "rails"
require "active_model/railtie"
module CouchRest
# = Active Record Railtie
class ModelRailtie < Rails::Railtie
config.generators.orm :couchrest_model
config.generators.test_framework :test_unit, :fixture => false
def self.generator
config.respond_to?(:app_generators) ? :app_generators : :generators
end
config.send(generator).orm :couchrest_model
config.send(generator).test_framework :test_unit, :fixture => false
initializer "couchrest_model.configure_default_connection" do
CouchRest::Model::Base.configure do |conf|

View file

@ -33,7 +33,6 @@ require "couchrest/model/property_protection"
require "couchrest/model/properties"
require "couchrest/model/casted_array"
require "couchrest/model/casted_hash"
require "couchrest/model/casted_model"
require "couchrest/model/validations"
require "couchrest/model/callbacks"
require "couchrest/model/document_queries"
@ -58,10 +57,10 @@ require "couchrest/model/core_extensions/hash"
require "couchrest/model/core_extensions/time_parsing"
# Base libraries
require "couchrest/model/casted_model"
require "couchrest/model/embeddable"
require "couchrest/model/base"
# Add rails support *after* everything has loaded
# Add rails support *after* everything has loaded
if defined?(Rails)
require "couchrest/railtie"
end

View file

@ -0,0 +1,18 @@
require 'rails/generators/couchrest_model'
module CouchrestModel
module Generators
class ConfigGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
def app_name
Rails::Application.subclasses.first.parent.to_s.underscore
end
def copy_configuration_file
template 'couchdb.yml', File.join('config', "couchdb.yml")
end
end
end
end

View file

@ -0,0 +1,21 @@
development: &development
protocol: 'http'
host: localhost
port: 5984
prefix: <%= app_name %>
suffix: development
username:
password:
test:
<<: *development
suffix: test
production:
protocol: 'https'
host: localhost
port: 5984
prefix: <%= app_name %>
suffix: production
username: root
password: 123

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 :slug, :read_only => true
property :user_id
property :title
property :tags, [String]

View file

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

View file

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

View file

@ -1,6 +1,6 @@
class CatToy < Hash
include ::CouchRest::Model::CastedModel
class CatToy
include CouchRest::Model::Embeddable
property :name
@ -17,3 +17,7 @@ class Cat < CouchRest::Model::Base
property :number
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 File.join(FIXTURE_PATH, 'more', 'person')
require 'question'
require 'person'
class Course < CouchRest::Model::Base
use_database TEST_SERVER.default_database

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
include CouchRest::Model::Embeddable
end

View file

@ -1,5 +1,7 @@
class Person < Hash
include ::CouchRest::Model::CastedModel
require 'cat'
class Person
include ::CouchRest::Model::Embeddable
property :pet, Cat
property :name, [String]

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

7
spec/fixtures/models/question.rb vendored Normal file
View file

@ -0,0 +1,7 @@
class Question
include ::CouchRest::Model::Embeddable
property :q
property :a
end

View file

@ -1,5 +1,6 @@
require File.join(FIXTURE_PATH, 'more', 'client')
require File.join(FIXTURE_PATH, 'more', 'sale_entry')
require 'client'
require 'sale_entry'
class SaleInvoice < CouchRest::Model::Base
use_database DB

View file

@ -1,7 +0,0 @@
class Question < Hash
include ::CouchRest::Model::CastedModel
property :q
property :a
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 "rubygems"
require "rspec" # Satisfies Autotest and anyone else not using the Rake tasks
require "rspec"
require File.join(File.dirname(__FILE__), '..','lib','couchrest_model')
# check the following file to see how to use the spec'd features.
require 'couchrest_model'
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')
SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp')
@ -16,17 +21,6 @@ unless defined?(FIXTURE_PATH)
DB = TEST_SERVER.database(TESTDB)
end
class Basic < CouchRest::Model::Base
use_database TEST_SERVER.default_database
end
def reset_test_db!
DB.recreate! rescue nil
# Reset the Design Cache
Thread.current[:couchrest_design_cache] = {}
DB
end
RSpec.configure do |config|
config.before(:all) { reset_test_db! }
@ -39,6 +33,21 @@ RSpec.configure do |config|
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
use_database TEST_SERVER.default_database
end
def reset_test_db!
DB.recreate! rescue nil
# Reset the Design Cache
Thread.current[:couchrest_design_cache] = {}
DB
end
def couchdb_lucene_available?
lucene_path = "http://localhost:5985/"
url = URI.parse(lucene_path)

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
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'more', 'sale_invoice')
require 'spec_helper'
describe "Assocations" do

View file

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

View file

@ -1,11 +1,5 @@
# encoding: utf-8
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require File.join(FIXTURE_PATH, 'base')
require "spec_helper"
describe "Model Base" do
@ -49,6 +43,43 @@ describe "Model Base" do
@obj.database.should eql('database')
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
it "should call after_initialize callback if available" do
klass = Class.new(CouchRest::Model::Base)
klass.class_eval do # for ruby 1.8.7
property :name
after_initialize :set_name
def set_name; self.name = "foobar"; end
end
@doc = klass.new
@doc.name.should eql("foobar")
end
end
describe "ActiveModel compatability Basic" do
@ -90,14 +121,22 @@ describe "Model Base" do
describe "#persisted?" do
context "when the document is new" do
it "returns false" do
@obj.persisted?.should == false
@obj.persisted?.should be_false
end
end
context "when the document is not new" do
it "returns id" do
@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
@ -109,7 +148,57 @@ describe "Model Base" do
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
describe "comparisons" do
describe "#==" do
context "on saved document" 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
it "should always be false if one document not saved" do
p = Project.create(:name => 'test')
o = Project.new(:name => 'test')
p.should_not eql(o)
end
end
context "with new documents" do
it "should be true when attributes match" do
p = Project.new(:name => 'test')
o = Project.new(:name => 'test')
p.should eql(o)
end
it "should not be true when attributes don't match" do
p = Project.new(:name => 'test')
o = Project.new(:name => 'testing')
p.should_not eql(o)
end
end
end
end
describe "update attributes without saving" do
@ -232,7 +321,7 @@ describe "Model Base" do
WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete
@tmpl = WithTemplateAndUniqueID.new
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
end
it "should have fields set when new" do
@tmpl.preset.should == 'value'
@ -253,10 +342,10 @@ describe "Model Base" do
before(:all) do
WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('slug' => '4').save
end
it "should find all" do
rs = WithTemplateAndUniqueID.all
@ -274,9 +363,9 @@ describe "Model Base" do
end
it ".count should return the number of documents" do
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.count.should == 3
end
@ -285,14 +374,14 @@ describe "Model Base" do
describe "finding the first instance of a model" do
before(:each) do
@db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save
WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('slug' => '4').save
end
it "should find first" do
rs = WithTemplateAndUniqueID.first
rs['important-field'].should == "1"
rs['slug'].should == "1"
end
it "should return nil if no instances are found" do
WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
@ -370,14 +459,7 @@ describe "Model Base" do
end
end
describe "initialization" do
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
end
describe "recursive validation on a model" do
describe "recursive validation on a model" do
before :each do
reset_test_db!
@cat = Cat.new(:name => 'Sockington')

View file

@ -1,7 +1,4 @@
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 "spec_helper"
class Driver < CouchRest::Model::Base
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
# Note: no use_database here

View file

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

View file

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

View file

@ -1,7 +1,7 @@
# 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
@class = Class.new(CouchRest::Model::Base)

View file

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

View file

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

View file

@ -1,12 +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', '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
include CouchRest::Model::CastedModel
property :name
property :details, Object, :default => {}
@ -241,6 +235,14 @@ describe "Dirty" do
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
should_not_change_array do |array, obj|
array.clear
@ -249,6 +251,50 @@ describe "Dirty" do
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
should_change_array do |array, obj|
array.push("keyword")

View file

@ -1,26 +1,25 @@
# 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
include CouchRest::Model::CastedModel
class WithCastedModelMixin
include CouchRest::Model::Embeddable
property :name
property :no_value
property :details, Object, :default => {}
property :casted_attribute, WithCastedModelMixin
end
class OldFashionedMixin < Hash
include CouchRest::Model::CastedModel
property :name
end
class DummyModel < CouchRest::Model::Base
use_database TEST_SERVER.default_database
raise "Default DB not set" if TEST_SERVER.default_database.nil?
property :casted_attribute, WithCastedModelMixin
property :keywords, [String]
property :old_casted_attribute, OldFashionedMixin
property :sub_models do |child|
child.property :title
end
@ -29,8 +28,8 @@ class DummyModel < CouchRest::Model::Base
end
end
class WithCastedCallBackModel < Hash
include CouchRest::Model::CastedModel
class WithCastedCallBackModel
include CouchRest::Model::Embeddable
property :name
property :run_before_validation
property :run_after_validation
@ -51,19 +50,7 @@ class CastedCallbackDoc < CouchRest::Model::Base
property :callback_model, WithCastedCallBackModel
end
describe CouchRest::Model::CastedModel do
describe "A non hash class including CastedModel" do
it "should fail raising and include error" do
lambda do
class NotAHashButWithCastedModelMixin
include CouchRest::CastedModel
property :name
end
end.should raise_error
end
end
describe CouchRest::Model::Embeddable do
describe "isolated" do
before(:each) do
@ -82,7 +69,27 @@ describe CouchRest::Model::CastedModel do
it "should always return base_doc? as false" do
@obj.base_doc?.should be_false
end
it "should call after_initialize callback if available" do
klass = Class.new do
include CouchRest::Model::CastedModel
after_initialize :set_name
property :name
def set_name; self.name = "foobar"; end
end
@obj = klass.new
@obj.name.should eql("foobar")
end
it "should allow override of initialize with super" do
klass = Class.new do
include CouchRest::Model::Embeddable
after_initialize :set_name
property :name
def set_name; self.name = "foobar"; end
def initialize(attrs = {}); super(); end
end
@obj = klass.new
@obj.name.should eql("foobar")
end
end
describe "casted as an attribute, but without a value" do
@ -162,6 +169,33 @@ describe CouchRest::Model::CastedModel do
end
end
# Basic testing for an old fashioned casted hash
describe "old hash casted as attribute" do
before :each do
@obj = DummyModel.new(:old_casted_attribute => {:name => 'Testing'})
@casted_obj = @obj.old_casted_attribute
end
it "should be available from its parent" do
@casted_obj.should be_an_instance_of(OldFashionedMixin)
end
it "should have the getters defined" do
@casted_obj.name.should == 'Testing'
end
it "should know who casted it" do
@casted_obj.casted_by.should == @obj
end
it "should know which property casted it" do
@casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'old_casted_attribute'}
end
it "should return nil for the unknown attribute" do
@casted_obj["unknown"].should be_nil
end
end
describe "casted as an array of a different type" do
before(:each) do
@obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])

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
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'base')
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require File.join(FIXTURE_PATH, 'more', 'card')
require 'spec_helper'
describe "Model Persistence" do
describe CouchRest::Model::Persistence do
before(:each) do
@obj = WithDefaultValues.new
@ -34,8 +29,8 @@ describe "Model Persistence" do
describe "basic saving and retrieving" do
it "should work fine" do
@obj.name = "should be easily saved and retrieved"
@obj.save
saved_obj = WithDefaultValues.get(@obj.id)
@obj.save!
saved_obj = WithDefaultValues.get!(@obj.id)
saved_obj.should_not be_nil
end
@ -81,6 +76,18 @@ describe "Model Persistence" do
article.should_not be_new
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
doc = WithCallBacks.create(:name => 'my other test')
doc.run_before_create.should be_true
@ -210,34 +217,34 @@ describe "Model Persistence" do
it "should require the field" do
lambda{@templated.save}.should raise_error
@templated['important-field'] = 'very-important'
@templated['slug'] = 'very-important'
@templated.save.should be_true
end
it "should save with the id" do
@templated['important-field'] = 'very-important'
@templated['slug'] = 'very-important'
@templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated
end
it "should not change the id on update" do
@templated['important-field'] = 'very-important'
@templated['slug'] = 'very-important'
@templated.save.should be_true
@templated['important-field'] = 'not-important'
@templated['slug'] = 'not-important'
@templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important')
t.id.should == @templated.id
end
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
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
lambda{WithTemplateAndUniqueID.new('slug' => 'very-important').save}.should raise_error
end
it "should set the id" do
@templated['important-field'] = 'very-important'
@templated['slug'] = 'very-important'
@templated.save.should be_true
@templated.id.should == 'very-important'
end
@ -245,22 +252,31 @@ describe "Model Persistence" do
describe "destroying an instance" do
before(:each) do
@dobj = Basic.new
@dobj = Event.new
@dobj.save.should be_true
end
it "should return true" do
result = @dobj.destroy
result.should be_true
end
it "should be resavable" do
@dobj.destroy
@dobj.rev.should be_nil
@dobj.id.should be_nil
@dobj.save.should be_true
end
it "should make it go away" do
@dobj.destroy
lambda{Basic.get!(@dobj.id)}.should raise_error
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
@ -340,6 +356,27 @@ describe "Model Persistence" do
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
it "should run the after filter after saving" do
@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

View file

@ -1,17 +1,7 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
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')
require 'spec_helper'
describe "Model properties" do
describe CouchRest::Model::Property do
before(:each) do
reset_test_db!
@ -72,15 +62,23 @@ describe "Model properties" do
@card.updated_at.should_not be_nil
end
describe "#as_json" do
describe "#as_couch_json" do
it "should provide a simple hash from model" do
@card.as_json.class.should eql(Hash)
@card.as_couch_json.class.should eql(Hash)
end
it "should remove properties from Hash if value is nil" do
@card.last_name = nil
@card.as_json.keys.include?('last_name').should be_false
@card.as_couch_json.keys.include?('last_name').should be_false
end
end
describe "#as_json" do
it "should provide a simple hash from model" do
@card.as_json.class.should eql(Hash)
end
it "should pass options to Active Support's as_json" do
@ -239,6 +237,16 @@ describe "Model properties" do
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
before(:each) do
@ -265,9 +273,9 @@ describe "properties of array of casted models" do
end
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.length.should eql(2)
@course.questions.last.q.should eql('Test2')
@course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
@course.questions.length.should eql(3)
@course.questions.last.q.should eql('Test10')
@course.questions.last.class.should eql(Question)
end
@ -284,7 +292,7 @@ describe "properties of array of casted models" do
it "should raise an error if attempting to set single value for array type" do
lambda {
@course.questions = Question.new(:q => 'test1')
}.should raise_error
}.should raise_error(/Expecting an array/)
end
@ -315,6 +323,28 @@ describe "a casted model retrieved from the database" do
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
it "should provide name as string" do
@ -420,15 +450,18 @@ describe "Property Class" do
ary.last.should eql(Date.new(2011, 05, 22))
end
it "should raise and error if value is array when type is not" do
property = CouchRest::Model::Property.new(:test, Date)
it "should cast an object that provides an array" do
prop = Class.new do
attr_accessor :ary
def initialize(val); self.ary = val; end
def as_json; ary; end
end
property = CouchRest::Model::Property.new(:test, prop)
parent = mock("FooClass")
lambda {
cast = property.cast(parent, [Date.new(2010, 6, 1)])
}.should raise_error
cast = property.cast(parent, [1, 2])
cast.ary.should eql([1, 2])
end
it "should set parent as casted_by object in CastedArray" do
property = CouchRest::Model::Property.new(:test, [Object])
parent = mock("FooObject")

View file

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

View file

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

View file

@ -1,8 +1,5 @@
# encoding: utf-8
require File.expand_path('../../spec_helper', __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'course')
require 'spec_helper'
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')
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 CouchRest::Model::Validations do
describe "Uniqueness" do

View file

@ -1,10 +1,6 @@
require File.expand_path("../../spec_helper", __FILE__)
require File.join(FIXTURE_PATH, 'more', 'cat')
require File.join(FIXTURE_PATH, 'more', 'person')
require File.join(FIXTURE_PATH, 'more', 'article')
require File.join(FIXTURE_PATH, 'more', 'course')
require "spec_helper"
describe "Model views" do
describe CouchRest::Model::Views do
class Unattached < CouchRest::Model::Base
property :title
@ -18,7 +14,6 @@ describe "Model views" do
end
end
describe "ClassMethods" do
# NOTE! Add more unit tests!
@ -179,6 +174,21 @@ describe "Model views" do
end
describe "#method_missing for find_by methods" do
before(:all) { reset_test_db! }
specify { Course.should respond_to :find_by_title_and_active }
specify { Course.should respond_to :by_title }
specify "#method should work in ruby 1.9, but not 1.8" do
if RUBY_VERSION >= "1.9"
Course.method(:find_by_title_and_active).should be_a Method
else
expect { Course.method(:find_by_title_and_active) }.to raise_error(NameError)
end
end
end
describe "a ducktype view" do
before(:all) do
reset_test_db!