Compare commits
74 Commits
v1.1.0.bet
...
master
Author | SHA1 | Date |
---|---|---|
Sam Lown | 819ddb7643 | |
Peter Williams | 447b11a397 | |
Peter Williams | 88bb413ec2 | |
Peter Williams | 39c60d77d2 | |
Peter Williams | f2c16144b0 | |
Marcos Tapajos | 1c695f58bf | |
Marcos Tapajos | 2f00599209 | |
Marcos Tapajós | 29de79290f | |
Marcos Tapajós | 44e09f6083 | |
Kim Burgestrand | 465c0681e2 | |
Kim Burgestrand | 72e3ac37d6 | |
Sam Lown | 80e5ed2767 | |
Sam Lown | 3258ac22e9 | |
Sam Lown | 3f1b2ea0c6 | |
Sam Lown | 9d724aee47 | |
Sam Lown | 8efa5208da | |
Sam Lown | 05ed7b127f | |
Sam Lown | e40b96519e | |
Sam Lown | 4ba85b4cff | |
Sam Lown | e91812ca53 | |
Sam Lown | a8a1372e57 | |
Sam Lown | 31b52ba012 | |
Sam Lown | ca1d1cd51d | |
Sam Lown | 406d2bc791 | |
Lucas Renan | 9f7ce5d49b | |
Sam Lown | dca47ac3b2 | |
Chase DuBois | b3a005b86d | |
Chase DuBois | f6d88530b7 | |
Sam Lown | 98772ae98a | |
Sam Lown | 778e486328 | |
Sam Lown | 7875113d95 | |
Sam Lown | ca932df5ba | |
Sam Lown | 3579e0e334 | |
Kostiantyn Kahanskyi | 1b5c431053 | |
Sam Lown | ea4325f5bf | |
Sam Lown | 7e054fd948 | |
Sam Lown | 7c7ee2c2b1 | |
Marcos Tapajós | d99150547c | |
Kostiantyn Kahanskyi | 0bb00860d1 | |
Marcos Tapajós | f244b51fbf | |
Kostiantyn Kahanskyi | d50d47c32a | |
Sam Lown | a55cf56213 | |
Kostiantyn Kahanskyi | 5cee1734da | |
Sam Lown | 6e025bb256 | |
Sam Lown | f5be6c7679 | |
Sam Lown | de1cab1271 | |
Sam Lown | 634fdf8b2f | |
Sam Lown | 36157a01d3 | |
Sam Lown | 84d7063608 | |
Peter Williams | 53bc7637e2 | |
Peter Williams | 9369ad96d3 | |
Peter Williams | 2b9a5193b4 | |
Marcos Tapajós | 4e19fdfdda | |
Peter Williams | 9b8564d9a8 | |
Peter Williams | f28cce1f0a | |
Lucas Renan | ccafde77ab | |
Sam Lown | fcd9e2ba8e | |
Sam Lown | d56179aa6b | |
Sam Lown | 938bf2cf2c | |
Lucas Renan | a400d46333 | |
Sam Lown | fba1e53e20 | |
Sam Lown | 9754f4633c | |
Sam Lown | 1ef25c5015 | |
Lucas Renan | f11a99dbfb | |
Sam Lown | c5ae042c76 | |
Davide D'Agostino | f8769400b6 | |
Davide D'Agostino | 14e96367a5 | |
Lucas Renan | c3b7cc316d | |
Sam Lown | 3f7b1d38c1 | |
Peter Williams | c9d2611bb7 | |
Sam Lown | 1309fa7082 | |
Peter Williams | 2669de9511 | |
Peter Williams | 2b0dc2b779 | |
Peter Williams | 7f1f05cbca |
|
@ -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
|
||||
|
|
21
Rakefile
21
Rakefile
|
@ -1,23 +1,20 @@
|
|||
require 'rubygems'
|
||||
require 'bundler'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
require 'rake'
|
||||
require 'rspec/core/rake_task'
|
||||
require "rake/rdoctask"
|
||||
|
||||
require 'rspec'
|
||||
require 'rspec/core/rake_task'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
desc "Run all specs"
|
||||
Rspec::Core::RakeTask.new(:spec) do |spec|
|
||||
spec.rspec_opts = ["--color"]
|
||||
spec.pattern = 'spec/**/*_spec.rb'
|
||||
RSpec::Core::RakeTask.new(:spec) do |spec|
|
||||
spec.rspec_opts = ["--color"]
|
||||
spec.pattern = 'spec/**/*_spec.rb'
|
||||
end
|
||||
|
||||
desc "Print specdocs"
|
||||
Rspec::Core::RakeTask.new(:doc) do |spec|
|
||||
spec.rspec_opts = ["--format", "specdoc"]
|
||||
spec.pattern = 'spec/*_spec.rb'
|
||||
RSpec::Core::RakeTask.new(:doc) do |spec|
|
||||
spec.rspec_opts = ["--format", "specdoc"]
|
||||
spec.pattern = 'spec/*_spec.rb'
|
||||
end
|
||||
|
||||
desc "Generate the rdoc"
|
||||
|
@ -38,4 +35,4 @@ module Rake
|
|||
end
|
||||
|
||||
Rake.remove_task("github:release")
|
||||
Rake.remove_task("release")
|
||||
Rake.remove_task("release")
|
|
@ -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 = [
|
||||
|
@ -17,19 +17,20 @@ Gem::Specification.new do |s|
|
|||
s.homepage = %q{http://github.com/couchrest/couchrest_model}
|
||||
s.rubygems_version = %q{1.3.7}
|
||||
s.summary = %q{Extends the CouchRest Document for advanced modelling.}
|
||||
|
||||
|
||||
s.files = `git ls-files`.split("\n")
|
||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
||||
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.0")
|
||||
s.add_dependency(%q<activemodel>, "~> 3.0")
|
||||
s.add_dependency(%q<tzinfo>, "~> 0.3.22")
|
||||
s.add_dependency(%q<railties>, "~> 3.0.0")
|
||||
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
|
||||
|
||||
|
|
52
history.md
52
history.md
|
@ -1,5 +1,57 @@
|
|||
# CouchRest Model Change History
|
||||
|
||||
## 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.
|
||||
* Rails 3.1 support (Peter Williams)
|
||||
* Initialization blocks when creating new models (Peter Williams)
|
||||
* Removed railties dependency (DAddYE)
|
||||
* 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
|
||||
|
||||
* Major changes:
|
||||
|
|
|
@ -153,7 +153,7 @@ module CouchRest
|
|||
def #{attrib}(reload = false)
|
||||
return @#{attrib} unless @#{attrib}.nil? or reload
|
||||
ary = self.#{options[:foreign_key]}.collect{|i| #{options[:proxy]}.get(i)}
|
||||
@#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
|
||||
@#{attrib} = ::CouchRest::Model::CollectionOfProxy.new(ary, find_property('#{options[:foreign_key]}'), self)
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -161,7 +161,7 @@ module CouchRest
|
|||
def create_collection_of_setter(attrib, options)
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}=(value)
|
||||
@#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
|
||||
@#{attrib} = ::CouchRest::Model::CollectionOfProxy.new(value, find_property('#{options[:foreign_key]}'), self)
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -169,67 +169,63 @@ module CouchRest
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# Special proxy for a collection of items so that adding and removing
|
||||
# to the list automatically updates the associated property.
|
||||
class CollectionOfProxy < Array
|
||||
attr_accessor :property
|
||||
attr_accessor :casted_by
|
||||
# Special proxy for a collection of items so that adding and removing
|
||||
# to the list automatically updates the associated property.
|
||||
class CollectionOfProxy < CastedArray
|
||||
|
||||
def initialize(array, casted_by, property)
|
||||
self.property = property
|
||||
self.casted_by = casted_by
|
||||
(array ||= []).compact!
|
||||
casted_by[property.to_s] = [] # replace the original array!
|
||||
array.compact.each do |obj|
|
||||
check_obj(obj)
|
||||
casted_by[property.to_s] << obj.id
|
||||
def initialize(array, property, parent)
|
||||
(array ||= []).compact!
|
||||
super(array, property, parent)
|
||||
casted_by[casted_by_property.to_s] = [] # replace the original array!
|
||||
array.compact.each do |obj|
|
||||
check_obj(obj)
|
||||
casted_by[casted_by_property.to_s] << obj.id
|
||||
end
|
||||
end
|
||||
super(array)
|
||||
end
|
||||
|
||||
def << obj
|
||||
check_obj(obj)
|
||||
casted_by[property.to_s] << obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def push(obj)
|
||||
check_obj(obj)
|
||||
casted_by[property.to_s].push obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def unshift(obj)
|
||||
check_obj(obj)
|
||||
casted_by[property.to_s].unshift obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def []= index, obj
|
||||
check_obj(obj)
|
||||
casted_by[property.to_s][index] = obj.id
|
||||
super(index, obj)
|
||||
end
|
||||
def << obj
|
||||
check_obj(obj)
|
||||
casted_by[casted_by_property.to_s] << obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def pop
|
||||
casted_by[property.to_s].pop
|
||||
super
|
||||
end
|
||||
|
||||
def shift
|
||||
casted_by[property.to_s].shift
|
||||
super
|
||||
end
|
||||
def push(obj)
|
||||
check_obj(obj)
|
||||
casted_by[casted_by_property.to_s].push obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
protected
|
||||
def unshift(obj)
|
||||
check_obj(obj)
|
||||
casted_by[casted_by_property.to_s].unshift obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def []= index, obj
|
||||
check_obj(obj)
|
||||
casted_by[casted_by_property.to_s][index] = obj.id
|
||||
super(index, obj)
|
||||
end
|
||||
|
||||
def pop
|
||||
casted_by[casted_by_property.to_s].pop
|
||||
super
|
||||
end
|
||||
|
||||
def shift
|
||||
casted_by[casted_by_property.to_s].shift
|
||||
super
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def check_obj(obj)
|
||||
raise "Object cannot be added to #{casted_by.class.to_s}##{casted_by_property.to_s} collection unless saved" if obj.new?
|
||||
end
|
||||
|
||||
def check_obj(obj)
|
||||
raise "Object cannot be added to #{casted_by.class.to_s}##{property.to_s} collection unless saved" if obj.new?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -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,18 +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
|
||||
#
|
||||
def initialize(doc = {}, options = {})
|
||||
doc = prepare_all_attributes(doc, options)
|
||||
# set the instances database, if provided
|
||||
# If a block is provided the new model will be passed into the
|
||||
# block so that it can be populated.
|
||||
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
|
||||
|
||||
|
||||
|
@ -75,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
|
||||
|
@ -96,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -50,6 +50,22 @@ 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)
|
||||
obj
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def instantiate_and_cast(obj, change = true)
|
||||
|
|
|
@ -87,7 +87,14 @@ module CouchRest
|
|||
doc
|
||||
end
|
||||
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
|
||||
|
||||
def has_view?(view)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -7,7 +7,11 @@ module CouchRest
|
|||
module ClassMethods
|
||||
|
||||
def design_doc
|
||||
@design_doc ||= ::CouchRest::Design.new(default_design_doc)
|
||||
@design_doc ||= if auto_update_design_doc
|
||||
::CouchRest::Design.new(default_design_doc)
|
||||
else
|
||||
stored_design_doc || ::CouchRest::Design.new(default_design_doc)
|
||||
end
|
||||
end
|
||||
|
||||
def design_doc_id
|
||||
|
@ -75,16 +79,25 @@ module CouchRest
|
|||
# If auto updates enabled, check checksum cache
|
||||
return design_doc if auto_update_design_doc && design_doc_cache_checksum(db) == checksum
|
||||
|
||||
# Load up the stored doc (if present), update, and save
|
||||
saved = stored_design_doc(db)
|
||||
if saved
|
||||
if force || saved['couchrest-hash'] != checksum
|
||||
saved.merge!(design_doc)
|
||||
db.save_doc(saved)
|
||||
retries = 1
|
||||
begin
|
||||
# Load up the stored doc (if present), update, and save
|
||||
saved = stored_design_doc(db)
|
||||
if saved
|
||||
if force || saved['couchrest-hash'] != checksum
|
||||
saved.merge!(design_doc)
|
||||
db.save_doc(saved)
|
||||
@design_doc = saved # update memo to point to the document we actually saved
|
||||
end
|
||||
else
|
||||
design_doc.delete('_rev') # This is a new document and so doesn't have a revision yet
|
||||
db.save_doc(design_doc)
|
||||
end
|
||||
else
|
||||
db.save_doc(design_doc)
|
||||
design_doc.delete('_rev') # Prevent conflicts, never store rev as DB specific
|
||||
rescue RestClient::Conflict
|
||||
# if we get a conflict retry the operation...
|
||||
raise if retries < 1
|
||||
retries -= 1
|
||||
retry
|
||||
end
|
||||
|
||||
# Ensure checksum cached for next attempt if using auto updates
|
||||
|
@ -108,8 +121,6 @@ module CouchRest
|
|||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
end # module ClassMethods
|
||||
|
||||
end
|
||||
|
|
|
@ -58,10 +58,11 @@ module CouchRest
|
|||
self.model = model
|
||||
end
|
||||
|
||||
# Define a view and generate a method that will provide a new
|
||||
# View instance when requested.
|
||||
# Generate a method that will provide a new View instance when
|
||||
# requested. This will also define the view in CouchDB unless
|
||||
# auto_update_design_doc is disabled.
|
||||
def view(name, opts = {})
|
||||
View.create(model, name, opts)
|
||||
View.create(model, name, opts) if model.auto_update_design_doc
|
||||
create_view_method(name)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module DocumentQueries
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
|
||||
|
||||
# Load all documents that have the model_type_key's field equal to the
|
||||
# name of the current class. Take the standard set of
|
||||
# CouchRest::Database#view options.
|
||||
|
@ -73,7 +70,7 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
alias :find :get
|
||||
|
||||
|
||||
# Load a document from the database by id
|
||||
# An exception will be raised if the document isn't found
|
||||
#
|
||||
|
@ -86,9 +83,12 @@ module CouchRest
|
|||
# id<String, Integer>:: Document ID
|
||||
# db<Database>:: optional option to pass a custom database to use
|
||||
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
|
||||
build_from_database(doc)
|
||||
rescue RestClient::ResourceNotFound
|
||||
raise CouchRest::Model::DocumentNotFound
|
||||
end
|
||||
alias :find! :get!
|
||||
|
||||
|
|
|
@ -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
|
|
@ -19,5 +19,7 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DocumentNotFound < Errors::CouchRestModelError; end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,25 +5,17 @@ module CouchRest
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
||||
extlib_inheritable_accessor(:properties_by_name) unless self.respond_to?(:properties_by_name)
|
||||
class_attribute(:properties) unless self.respond_to?(:properties)
|
||||
class_attribute(:properties_by_name) unless self.respond_to?(:properties_by_name)
|
||||
self.properties ||= []
|
||||
self.properties_by_name ||= {}
|
||||
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
|
||||
|
||||
# Returns the Class properties
|
||||
#
|
||||
# ==== Returns
|
||||
# Array:: the list of properties for model's class
|
||||
def properties
|
||||
self.class.properties
|
||||
end
|
||||
|
||||
# Returns all the class's properties as a Hash where the key is the name
|
||||
# of the property.
|
||||
def properties_by_name
|
||||
self.class.properties_by_name
|
||||
# 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
|
||||
|
@ -90,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)
|
||||
|
@ -111,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
|
||||
|
@ -161,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
|
||||
|
@ -180,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 }
|
||||
|
@ -203,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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -64,8 +59,28 @@ module CouchRest::Model
|
|||
end
|
||||
end
|
||||
|
||||
# Initialize a new instance of a property's type ready to be
|
||||
# used. If a proc is defined for the init method, it will be used instead of
|
||||
# a normal call to the class.
|
||||
def build(*args)
|
||||
raise StandardError, "Cannot build property without a class" if @type_class.nil?
|
||||
if @init_method.is_a?(Proc)
|
||||
@init_method.call(*args)
|
||||
else
|
||||
@type_class.send(@init_method, *args)
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#
|
||||
# Extend CouchRest's normal database delete! method to ensure any caches are
|
||||
# also emptied. Given that this is a rare event, and the consequences are not
|
||||
# very severe, we just completely empty the cache.
|
||||
#
|
||||
CouchRest::Database.class_eval do
|
||||
|
||||
def delete!
|
||||
Thread.current[:couchrest_design_cache] = { }
|
||||
CouchRest.delete @root
|
||||
end
|
||||
|
||||
end
|
|
@ -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)}
|
||||
|
|
|
@ -14,8 +14,7 @@ module CouchRest
|
|||
elsif [String, TrueClass, Integer, Float, BigDecimal, DateTime, Time, Date, Class].include?(klass)
|
||||
send('typecast_to_'+klass.to_s.downcase, value)
|
||||
else
|
||||
# Allow the init_method to be defined as a Proc for advanced conversion
|
||||
property.init_method.is_a?(Proc) ? property.init_method.call(value) : klass.send(property.init_method, value)
|
||||
property.build(value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,22 +13,33 @@ 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
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
# 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
|
||||
# documents.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module Validations
|
||||
|
||||
|
||||
# Validates if a field is unique
|
||||
class UniquenessValidator < ActiveModel::EachValidator
|
||||
|
||||
|
@ -11,29 +11,33 @@ module CouchRest
|
|||
# or add one if necessary.
|
||||
def setup(model)
|
||||
@model = model
|
||||
if options[:view].blank?
|
||||
attributes.each do |attribute|
|
||||
opts = merge_view_options(attribute)
|
||||
|
||||
if model.respond_to?(:has_view?) && !model.has_view?(opts[:view_name])
|
||||
opts[:keys] << {:allow_nil => true}
|
||||
model.view_by(*opts[:keys])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_each(document, attribute, value)
|
||||
keys = [attribute]
|
||||
unless options[:scope].nil?
|
||||
keys = (options[:scope].is_a?(Array) ? options[:scope] : [options[:scope]]) + keys
|
||||
end
|
||||
values = keys.map{|k| document.send(k)}
|
||||
values = values.first if values.length == 1
|
||||
opts = merge_view_options(attribute)
|
||||
|
||||
view_name = options[:view].nil? ? "by_#{keys.join('_and_')}" : options[:view]
|
||||
values = opts[:keys].map{|k| document.send(k)}
|
||||
values = values.first if values.length == 1
|
||||
|
||||
model = (document.respond_to?(:model_proxy) && document.model_proxy ? document.model_proxy : @model)
|
||||
# Determine the base of the search
|
||||
base = options[:proxy].nil? ? model : document.instance_eval(options[:proxy])
|
||||
base = opts[:proxy].nil? ? model : document.instance_eval(opts[:proxy])
|
||||
|
||||
if base.respond_to?(:has_view?) && !base.has_view?(view_name)
|
||||
raise "View #{document.class.name}.#{options[:view]} does not exist!" unless options[:view].nil?
|
||||
keys << {:allow_nil => true}
|
||||
model.view_by(*keys)
|
||||
if base.respond_to?(:has_view?) && !base.has_view?(opts[:view_name])
|
||||
raise "View #{document.class.name}.#{opts[:view_name]} does not exist for validation!"
|
||||
end
|
||||
|
||||
rows = base.view(view_name, :key => values, :limit => 2, :include_docs => false)['rows']
|
||||
rows = base.view(opts[:view_name], :key => values, :limit => 2, :include_docs => false)['rows']
|
||||
return if rows.empty?
|
||||
|
||||
unless document.new?
|
||||
|
@ -47,6 +51,17 @@ module CouchRest
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def merge_view_options(attr)
|
||||
keys = [attr]
|
||||
keys.unshift(*options[:scope]) unless options[:scope].nil?
|
||||
|
||||
view_name = options[:view].nil? ? "by_#{keys.join('_and_')}" : options[:view]
|
||||
|
||||
options.merge({:keys => keys, :view_name => view_name})
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -72,9 +72,12 @@ module CouchRest
|
|||
# <tt>spec/couchrest/more/extended_doc_spec.rb</tt>.
|
||||
|
||||
def view_by(*keys)
|
||||
return unless auto_update_design_doc
|
||||
|
||||
opts = keys.pop if keys.last.is_a?(Hash)
|
||||
opts ||= {}
|
||||
ducktype = opts.delete(:ducktype)
|
||||
|
||||
unless ducktype || opts[:map]
|
||||
opts[:guards] ||= []
|
||||
opts[:guards].push "(doc['#{model_type_key}'] == '#{self.to_s}')"
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
require 'active_model'
|
||||
require "active_model/callbacks"
|
||||
require "active_model/conversion"
|
||||
require "active_model/deprecated_error_methods"
|
||||
require "active_model/errors"
|
||||
require "active_model/naming"
|
||||
require "active_model/serialization"
|
||||
|
@ -34,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"
|
||||
|
@ -52,16 +50,17 @@ require "couchrest/model/designs/view"
|
|||
|
||||
# Monkey patches applied to couchrest
|
||||
require "couchrest/model/support/couchrest_design"
|
||||
require "couchrest/model/support/couchrest_database"
|
||||
|
||||
# Core Extensions
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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]
|
||||
|
|
@ -83,18 +83,32 @@ 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
|
||||
|
||||
class WithGetterAndSetterMethods < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
|
||||
property :other_arg
|
||||
def arg
|
||||
other_arg
|
||||
|
@ -107,7 +121,7 @@ end
|
|||
|
||||
class WithAfterInitializeMethod < CouchRest::Model::Base
|
||||
use_database TEST_SERVER.default_database
|
||||
|
||||
|
||||
property :some_value
|
||||
|
||||
def after_initialize
|
|
@ -1,3 +1,5 @@
|
|||
require 'person'
|
||||
|
||||
class Card < CouchRest::Model::Base
|
||||
# Set the default database to use
|
||||
use_database DB
|
|
@ -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
|
|
@ -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
|
|
@ -6,9 +6,9 @@ class Invoice < CouchRest::Model::Base
|
|||
property :client_name
|
||||
property :employee_name
|
||||
property :location
|
||||
|
||||
|
||||
# Validation
|
||||
validates_presence_of :client_name, :employee_name
|
||||
validates_presence_of :location, :message => "Hey stupid!, you forgot the location"
|
||||
|
||||
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class KeyChain < CouchRest::Model::Base
|
||||
use_database(DB)
|
||||
|
||||
property(:keys, Hash)
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
class Membership
|
||||
include CouchRest::Model::Embeddable
|
||||
|
||||
end
|
|
@ -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]
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
class Project < CouchRest::Model::Base
|
||||
use_database DB
|
||||
property :name, String
|
||||
timestamps!
|
||||
view_by :name
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
class Question
|
||||
include ::CouchRest::Model::Embeddable
|
||||
|
||||
property :q
|
||||
property :a
|
||||
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
require File.join(FIXTURE_PATH, 'more', 'client')
|
||||
require File.join(FIXTURE_PATH, 'more', 'sale_entry')
|
||||
class SaleInvoice < CouchRest::Model::Base
|
||||
require 'client'
|
||||
require 'sale_entry'
|
||||
|
||||
class SaleInvoice < CouchRest::Model::Base
|
||||
use_database DB
|
||||
|
||||
belongs_to :client
|
||||
|
@ -10,4 +11,4 @@ class SaleInvoice < CouchRest::Model::Base
|
|||
|
||||
property :date, Date
|
||||
property :price, Integer
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
class Question < Hash
|
||||
include ::CouchRest::Model::CastedModel
|
||||
|
||||
property :q
|
||||
property :a
|
||||
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
|
||||
describe CouchRest::Model::Validations do
|
||||
|
||||
let(:invoice) do
|
||||
Invoice.new()
|
||||
end
|
||||
end
|
|
@ -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,6 +21,21 @@ unless defined?(FIXTURE_PATH)
|
|||
DB = TEST_SERVER.database(TESTDB)
|
||||
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
|
||||
use_database TEST_SERVER.default_database
|
||||
end
|
||||
|
@ -27,17 +47,6 @@ def reset_test_db!
|
|||
DB
|
||||
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?
|
||||
lucene_path = "http://localhost:5985/"
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
@ -101,7 +99,7 @@ describe "Assocations" do
|
|||
it "should create an associated property and collection proxy" do
|
||||
@invoice.respond_to?('entry_ids').should be_true
|
||||
@invoice.respond_to?('entry_ids=').should be_true
|
||||
@invoice.entries.class.should eql(::CouchRest::CollectionOfProxy)
|
||||
@invoice.entries.class.should eql(::CouchRest::Model::CollectionOfProxy)
|
||||
end
|
||||
|
||||
it "should allow replacement of objects" do
|
||||
|
@ -154,6 +152,33 @@ describe "Assocations" do
|
|||
@invoice.entries.should be_empty
|
||||
end
|
||||
|
||||
# Account for dirty tracking
|
||||
describe "dirty tracking" do
|
||||
it "should register changes on push" do
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entries << @entries[0]
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register changes on pop" do
|
||||
@invoice.entries << @entries[0]
|
||||
@invoice.save
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entries.pop
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register id changes on push" do
|
||||
@invoice.entry_ids << @entries[0].id
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
it "should register id changes on pop" do
|
||||
@invoice.entry_ids << @entries[0].id
|
||||
@invoice.save
|
||||
@invoice.changed?.should be_false
|
||||
@invoice.entry_ids.pop
|
||||
@invoice.changed?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "proxy" do
|
||||
|
||||
it "should ensure new entries to proxy are matched" do
|
|
@ -1,4 +1,4 @@
|
|||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require 'spec_helper'
|
||||
|
||||
describe "Model attachments" do
|
||||
|
|
@ -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
|
||||
|
||||
|
@ -44,8 +38,50 @@ describe "Model Base" do
|
|||
@obj.database.should eql('database')
|
||||
end
|
||||
|
||||
it "should support initialization block" do
|
||||
@obj = Basic.new {|b| b.database = 'database'}
|
||||
@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
|
||||
|
||||
before(:each) do
|
||||
|
@ -85,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
|
||||
|
@ -104,9 +148,59 @@ 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
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
|
@ -147,7 +241,7 @@ describe "Model Base" do
|
|||
}.should_not raise_error
|
||||
@art.slug.should == "big-bad-danger"
|
||||
end
|
||||
|
||||
|
||||
#it "should not change other attributes if there is an error" do
|
||||
# lambda {
|
||||
# @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
|
||||
|
@ -155,7 +249,7 @@ describe "Model Base" do
|
|||
# @art['title'].should == "big bad danger"
|
||||
#end
|
||||
end
|
||||
|
||||
|
||||
describe "update attributes" do
|
||||
before(:each) do
|
||||
a = Article.get "big-bad-danger" rescue nil
|
||||
|
@ -170,7 +264,7 @@ describe "Model Base" do
|
|||
loaded['title'].should == "super danger"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "with default" do
|
||||
it "should have the default value set at initalization" do
|
||||
@obj.preset.should == {:right => 10, :top_align => false}
|
||||
|
@ -227,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'
|
||||
|
@ -248,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
|
||||
|
@ -269,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
|
||||
|
@ -280,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 }
|
||||
|
@ -365,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')
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require "spec_helper"
|
||||
|
||||
class UnattachedDoc < CouchRest::Model::Base
|
||||
# Note: no use_database here
|
||||
|
@ -123,6 +123,35 @@ describe "Proxy Class" do
|
|||
u.respond_to?(:database).should be_false
|
||||
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
|
||||
# Removed as unclear why this should happen as before my changes
|
||||
# this happend by accident, not explicitly.
|
|
@ -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
|
|
@ -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)
|
|
@ -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)
|
|
@ -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!
|
||||
|
@ -155,42 +152,75 @@ describe "Design Documents" do
|
|||
Article.by_date
|
||||
Article.stored_design_doc['_rev'].should eql(orig)
|
||||
end
|
||||
it "should recreate the design doc if database deleted" do
|
||||
Article.database.recreate!
|
||||
lambda { Article.by_date }.should_not raise_error(RestClient::ResourceNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
describe "when auto_update_design_doc false" do
|
||||
|
||||
before :all do
|
||||
Article.auto_update_design_doc = false
|
||||
Article.save_design_doc!
|
||||
end
|
||||
# We really do need a new class for each of example. If we try
|
||||
# to use the same class the examples interact with each in ways
|
||||
# that can hide failures because the design document gets cached
|
||||
# at the class level.
|
||||
let(:model_class) {
|
||||
class_name = "#{example.metadata[:full_description].gsub(/\s+/,'_').camelize}Model"
|
||||
doc = CouchRest::Document.new("_id" => "_design/#{class_name}")
|
||||
doc["language"] = "javascript"
|
||||
doc["views"] = {"all" => {"map" =>
|
||||
"function(doc) {
|
||||
if (doc['type'] == 'Article') {
|
||||
emit(doc['_id'],1);
|
||||
}
|
||||
}"},
|
||||
"by_name" => {"map" =>
|
||||
"function(doc) {
|
||||
if ((doc['type'] == '#{class_name}') && (doc['name'] != null)) {
|
||||
emit(doc['name'], null);
|
||||
}",
|
||||
"reduce" =>
|
||||
"function(keys, values, rereduce) {
|
||||
return sum(values);
|
||||
}"}}
|
||||
|
||||
DB.save_doc doc
|
||||
|
||||
after :all do
|
||||
Article.auto_update_design_doc = true
|
||||
end
|
||||
eval <<-KLASS
|
||||
class ::#{class_name} < CouchRest::Model::Base
|
||||
use_database DB
|
||||
self.auto_update_design_doc = false
|
||||
design do
|
||||
view :by_name
|
||||
end
|
||||
property :name, String
|
||||
end
|
||||
KLASS
|
||||
|
||||
it "will not send a request for the saved design doc" do
|
||||
Article.should_not_receive(:stored_design_doc)
|
||||
Article.by_date
|
||||
end
|
||||
class_name.constantize
|
||||
}
|
||||
|
||||
it "will not update stored design doc if view changed" do
|
||||
Article.by_date
|
||||
orig = Article.stored_design_doc
|
||||
design = Article.design_doc
|
||||
view = design['views']['by_date']['map']
|
||||
design['views']['by_date']['map'] = view + ' '
|
||||
Article.by_date
|
||||
Article.stored_design_doc['_rev'].should eql(orig['_rev'])
|
||||
model_class.by_name
|
||||
orig = model_class.stored_design_doc
|
||||
design = model_class.design_doc
|
||||
view = design['views']['by_name']['map']
|
||||
design['views']['by_name']['map'] = view + ' '
|
||||
model_class.by_name
|
||||
model_class.stored_design_doc['_rev'].should eql(orig['_rev'])
|
||||
end
|
||||
|
||||
it "will update stored design if forced" do
|
||||
Article.by_date
|
||||
orig = Article.stored_design_doc
|
||||
design = Article.design_doc
|
||||
view = design['views']['by_date']['map']
|
||||
design['views']['by_date']['map'] = view + ' '
|
||||
Article.save_design_doc!
|
||||
Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
||||
model_class.by_name
|
||||
orig = model_class.stored_design_doc
|
||||
design = model_class.design_doc
|
||||
view = design['views']['by_name']['map']
|
||||
design['views']['by_name']['map'] = view + ' '
|
||||
model_class.save_design_doc!
|
||||
model_class.stored_design_doc['_rev'].should_not eql(orig['_rev'])
|
||||
end
|
||||
|
||||
it "is able to use predefined views" do
|
||||
model_class.by_name(key: "special").all
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -198,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
|
|
@ -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
|
||||
|
@ -84,7 +83,23 @@ describe "Design" do
|
|||
@object.should_receive(:create_view_method).with('test')
|
||||
@object.view('test')
|
||||
end
|
||||
end
|
||||
|
||||
context "for model with auto_update_design_doc disabled " do
|
||||
class ::DesignModelAutoUpdateDesignDocDisabled < ::CouchRest::Model::Base
|
||||
self.auto_update_design_doc = false
|
||||
end
|
||||
|
||||
describe "#view" do
|
||||
before :each do
|
||||
@object = @klass.new(DesignModelAutoUpdateDesignDocDisabled)
|
||||
end
|
||||
|
||||
it "does not attempt to create view" do
|
||||
CouchRest::Model::Designs::View.should_not_receive(:create)
|
||||
@object.view('test')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#filter" do
|
|
@ -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")
|
|
@ -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é'])
|
|
@ -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
|
||||
|
||||
|
|
@ -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,11 +29,11 @@ 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
|
||||
|
||||
|
||||
it "should parse the Time attributes automatically" do
|
||||
@obj.name = "should parse the Time attributes automatically"
|
||||
@obj.set_by_proc.should be_an_instance_of(Time)
|
||||
|
@ -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,57 +217,66 @@ 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
|
||||
end
|
||||
|
||||
|
||||
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
|
|
@ -1,4 +1,4 @@
|
|||
require File.expand_path("../../spec_helper", __FILE__)
|
||||
require "spec_helper"
|
||||
|
||||
describe "Model Attributes" do
|
||||
|
|
@ -1,16 +1,7 @@
|
|||
# encoding: utf-8
|
||||
require File.expand_path('../../spec_helper', __FILE__)
|
||||
require File.join(FIXTURE_PATH, 'more', 'cat')
|
||||
require File.join(FIXTURE_PATH, 'more', 'person')
|
||||
require File.join(FIXTURE_PATH, 'more', 'card')
|
||||
require File.join(FIXTURE_PATH, 'more', 'invoice')
|
||||
require File.join(FIXTURE_PATH, 'more', 'service')
|
||||
require File.join(FIXTURE_PATH, 'more', 'event')
|
||||
require File.join(FIXTURE_PATH, 'more', '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!
|
||||
|
@ -71,6 +62,32 @@ describe "Model properties" do
|
|||
@card.updated_at.should_not be_nil
|
||||
end
|
||||
|
||||
describe "#as_couch_json" do
|
||||
|
||||
it "should provide a simple hash from model" do
|
||||
@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_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
|
||||
@card.last_name = "Aimonetti"
|
||||
@card.as_json(:only => 'last_name').should eql('last_name' => 'Aimonetti')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#read_attribute' do
|
||||
it "should let you use read_attribute method" do
|
||||
@card.last_name = "Aimonetti"
|
||||
|
@ -220,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
|
||||
|
@ -246,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
|
||||
|
||||
|
@ -265,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
|
||||
|
||||
|
||||
|
@ -296,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
|
||||
|
@ -339,6 +388,28 @@ describe "Property Class" do
|
|||
property.init_method.should eql('parse')
|
||||
end
|
||||
|
||||
describe "#build" do
|
||||
it "should allow instantiation of new object" do
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
obj = property.build(2011, 05, 21)
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should use init_method if provided" do
|
||||
property = CouchRest::Model::Property.new(:test, Date, :init_method => 'parse')
|
||||
obj = property.build("2011-05-21")
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should use init_method Proc if provided" do
|
||||
property = CouchRest::Model::Property.new(:test, Date, :init_method => Proc.new{|v| Date.parse(v)})
|
||||
obj = property.build("2011-05-21")
|
||||
obj.should eql(Date.new(2011, 05, 21))
|
||||
end
|
||||
it "should raise error if no class" do
|
||||
property = CouchRest::Model::Property.new(:test)
|
||||
lambda { property.build }.should raise_error(StandardError, /Cannot build/)
|
||||
end
|
||||
end
|
||||
|
||||
## Property Casting method. More thoroughly tested in typecast_spec.
|
||||
|
||||
describe "casting" do
|
||||
|
@ -367,14 +438,29 @@ describe "Property Class" do
|
|||
property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(CouchRest::Model::CastedArray)
|
||||
end
|
||||
|
||||
it "should raise and error if value is array when type is not" do
|
||||
property = CouchRest::Model::Property.new(:test, Date)
|
||||
parent = mock("FooClass")
|
||||
lambda {
|
||||
cast = property.cast(parent, [Date.new(2010, 6, 1)])
|
||||
}.should raise_error
|
||||
it "should allow instantion of model via CastedArray#build" do
|
||||
property = CouchRest::Model::Property.new(:dates, [Date])
|
||||
parent = Article.new
|
||||
ary = property.cast(parent, [])
|
||||
obj = ary.build(2011, 05, 21)
|
||||
ary.length.should eql(1)
|
||||
ary.first.should eql(Date.new(2011, 05, 21))
|
||||
obj = ary.build(2011, 05, 22)
|
||||
ary.length.should eql(2)
|
||||
ary.last.should eql(Date.new(2011, 05, 22))
|
||||
end
|
||||
|
||||
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")
|
||||
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])
|
|
@ -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
|
|
@ -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'
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
@ -16,7 +8,11 @@ describe "Validations" do
|
|||
before(:all) do
|
||||
@objs = ['title 1', 'title 2', 'title 3'].map{|t| WithUniqueValidation.create(:title => t)}
|
||||
end
|
||||
|
||||
|
||||
it "should create a new view if none defined before performing" do
|
||||
WithUniqueValidation.has_view?(:by_title).should be_true
|
||||
end
|
||||
|
||||
it "should validate a new unique document" do
|
||||
@obj = WithUniqueValidation.create(:title => 'title 4')
|
||||
@obj.new?.should_not be_true
|
||||
|
@ -35,6 +31,7 @@ describe "Validations" do
|
|||
@obj.should be_valid
|
||||
end
|
||||
|
||||
|
||||
it "should allow own view to be specified" do
|
||||
# validates_uniqueness_of :code, :view => 'all'
|
||||
WithUniqueValidationView.create(:title => 'title 1', :code => '1234')
|
||||
|
@ -50,6 +47,13 @@ describe "Validations" do
|
|||
}.should raise_error
|
||||
end
|
||||
|
||||
it "should not try to create a defined view" do
|
||||
WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar'
|
||||
WithUniqueValidationView.has_view?('fooobar').should be_false
|
||||
WithUniqueValidationView.has_view?('by_title').should be_false
|
||||
end
|
||||
|
||||
|
||||
it "should not try to create new view when already defined" do
|
||||
@obj = @objs[1]
|
||||
@obj.class.should_not_receive('view_by')
|
||||
|
@ -60,6 +64,11 @@ describe "Validations" do
|
|||
end
|
||||
|
||||
context "with a proxy parameter" do
|
||||
|
||||
it "should create a new view despite proxy" do
|
||||
WithUniqueValidationProxy.has_view?(:by_title).should be_true
|
||||
end
|
||||
|
||||
it "should be used" do
|
||||
@obj = WithUniqueValidationProxy.new(:title => 'test 6')
|
||||
proxy = @obj.should_receive('proxy').and_return(@obj.class)
|
|
@ -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
|
||||
|
@ -17,7 +13,6 @@ describe "Model views" do
|
|||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "ClassMethods" do
|
||||
# NOTE! Add more unit tests!
|
||||
|
@ -178,7 +173,22 @@ describe "Model views" do
|
|||
end
|
||||
|
||||
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!
|
Loading…
Reference in New Issue