Upgrading to use CouchRest 1.1.0.pre3 and new Hash-less design

This commit is contained in:
Sam Lown 2011-06-08 18:22:35 +02:00
parent 6e025bb256
commit 7c7ee2c2b1
19 changed files with 197 additions and 137 deletions

View file

@ -1 +1 @@
1.1.0.beta5 1.1.0.rc

View file

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

View file

@ -1,11 +1,11 @@
# CouchRest Model Change History # CouchRest Model Change History
## 1.1.0 - 2011-05-XX ## 1.1.0.rc - 2011-06-08
* New Features * New Features
* Properties with a nil value are now no longer sent to the database. * Properties with a nil value are now no longer sent to the database.
* Now possible to build new objects via CastedArray#build * Now possible to build new objects via CastedArray#build
* Implement #get! and #find! class methods * Implement #get! and #find! class methods
* Minor fixes * Minor fixes
* #as_json now correctly uses ActiveSupports methods. * #as_json now correctly uses ActiveSupports methods.
@ -18,6 +18,8 @@
* #destroy freezes object instead of removing _id and _rev, better for callbacks (pointer by karmi) * #destroy freezes object instead of removing _id and _rev, better for callbacks (pointer by karmi)
* #destroyed? method now available * #destroyed? method now available
* #reload no longer uses Hash#merge! which was causing issues with dirty tracking on casted models. (pointer by kostia) * #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!)
## 1.1.0.beta5 - 2011-04-30 ## 1.1.0.beta5 - 2011-04-30

View file

@ -1,6 +1,6 @@
module CouchRest module CouchRest
module Model module Model
class Base < Document class Base < CouchRest::Document
extend ActiveModel::Naming extend ActiveModel::Naming
@ -51,14 +51,15 @@ module CouchRest
# #
# If a block is provided the new model will be passed into the # If a block is provided the new model will be passed into the
# block so that it can be populated. # block so that it can be populated.
def initialize(doc = {}, options = {}) def initialize(attributes = {}, options = {})
doc = prepare_all_attributes(doc, options) super()
# set the instances database, if provided prepare_all_attributes(attributes, options)
# set the instance's database, if provided
self.database = options[:database] unless options[:database].nil? self.database = options[:database] unless options[:database].nil?
super(doc)
unless self['_id'] && self['_rev'] unless self['_id'] && self['_rev']
self[self.model_type_key] = self.class.to_s self[self.model_type_key] = self.class.to_s
end end
yield self if block_given? yield self if block_given?
after_initialize if respond_to?(:after_initialize) after_initialize if respond_to?(:after_initialize)
@ -79,16 +80,6 @@ module CouchRest
super super
end end
## Compatibility with ActiveSupport and older frameworks
# Hack so that CouchRest::Document, which descends from Hash,
# doesn't appear to Rails routing as a Hash of options
def is_a?(klass)
return false if klass == Hash
super
end
alias :kind_of? :is_a?
def persisted? def persisted?
!new? !new?
end end

View file

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

View file

@ -106,14 +106,17 @@ module CouchRest
module ClassMethods module ClassMethods
# Creates a new instance, bypassing attribute protection # Creates a new instance, bypassing attribute protection and
# uses the type field to determine which model to use to instanatiate
# the new object.
# #
# ==== Returns # ==== Returns
# a document instance # a document instance
# #
def build_from_database(doc = {}) def build_from_database(doc = {}, options = {}, &block)
base = (doc[model_type_key].blank? || doc[model_type_key] == self.to_s) ? self : doc[model_type_key].constantize src = doc[model_type_key]
base.new(doc, :directly_set_attributes => true) base = (src.blank? || src == self.to_s) ? self : src.constantize
base.new(doc, options.merge(:directly_set_attributes => true), &block)
end end
# Defines an instance and save it directly to the database # Defines an instance and save it directly to the database

View file

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

View file

@ -26,7 +26,7 @@ module CouchRest::Model
if type.is_a?(Array) if type.is_a?(Array)
if value.nil? if value.nil?
value = [] value = []
elsif [Hash, HashWithIndifferentAccess].include?(value.class) elsif value.is_a?(Hash)
# Assume provided as a Hash where key is index! # Assume provided as a Hash where key is index!
data = value data = value
value = [ ] value = [ ]
@ -39,7 +39,7 @@ module CouchRest::Model
arr = value.collect { |data| cast_value(parent, data) } arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray # allow casted_by calls to be passed up chain by wrapping in CastedArray
CastedArray.new(arr, self, parent) CastedArray.new(arr, self, parent)
elsif (type == Object || type == Hash) && (value.class == Hash) elsif (type == Object || type == Hash) && (value.is_a?(Hash))
# allow casted_by calls to be passed up chain by wrapping in CastedHash # allow casted_by calls to be passed up chain by wrapping in CastedHash
CastedHash[value, self, parent] CastedHash[value, self, parent]
elsif !value.nil? elsif !value.nil?

View file

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

View file

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

View file

@ -49,6 +49,32 @@ describe "Model Base" do
@obj.database.should eql('database') @obj.database.should eql('database')
end end
it "should only set defined properties" do
@doc = WithDefaultValues.new(:name => 'test', :foo => 'bar')
@doc['name'].should eql('test')
@doc['foo'].should be_nil
end
it "should set all properties with :directly_set_attributes option" do
@doc = WithDefaultValues.new({:name => 'test', :foo => 'bar'}, :directly_set_attributes => true)
@doc['name'].should eql('test')
@doc['foo'].should eql('bar')
end
it "should set the model type" do
@doc = WithDefaultValues.new()
@doc[WithDefaultValues.model_type_key].should eql('WithDefaultValues')
end
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
it "should call after_initialize after block" do
@doc = WithAfterInitializeMethod.new {|d| d.some_value = "foo"}
@doc['some_value'].should eql('foo')
end
end end
describe "ActiveModel compatability Basic" do describe "ActiveModel compatability Basic" do
@ -109,6 +135,20 @@ describe "Model Base" do
end end
end end
describe "#destroyed?" do
it "should be present" do
@obj.should respond_to(:destroyed?)
end
it "should return false with new object" do
@obj.destroyed?.should be_false
end
it "should return true after destroy" do
@obj.save
@obj.destroy
@obj.destroyed?.should be_true
end
end
end end
@ -232,7 +272,7 @@ describe "Model Base" do
WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete WithTemplateAndUniqueID.database.bulk_delete
@tmpl = WithTemplateAndUniqueID.new @tmpl = WithTemplateAndUniqueID.new
@tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1') @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'slug' => '1')
end end
it "should have fields set when new" do it "should have fields set when new" do
@tmpl.preset.should == 'value' @tmpl.preset.should == 'value'
@ -253,10 +293,10 @@ describe "Model Base" do
before(:all) do before(:all) do
WithTemplateAndUniqueID.all.map{|o| o.destroy} WithTemplateAndUniqueID.all.map{|o| o.destroy}
WithTemplateAndUniqueID.database.bulk_delete WithTemplateAndUniqueID.database.bulk_delete
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save WithTemplateAndUniqueID.new('slug' => '4').save
end end
it "should find all" do it "should find all" do
rs = WithTemplateAndUniqueID.all rs = WithTemplateAndUniqueID.all
@ -274,9 +314,9 @@ describe "Model Base" do
end end
it ".count should return the number of documents" do it ".count should return the number of documents" do
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.count.should == 3 WithTemplateAndUniqueID.count.should == 3
end end
@ -285,14 +325,14 @@ describe "Model Base" do
describe "finding the first instance of a model" do describe "finding the first instance of a model" do
before(:each) do before(:each) do
@db = reset_test_db! @db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
WithTemplateAndUniqueID.new('important-field' => '2').save WithTemplateAndUniqueID.new('slug' => '2').save
WithTemplateAndUniqueID.new('important-field' => '3').save WithTemplateAndUniqueID.new('slug' => '3').save
WithTemplateAndUniqueID.new('important-field' => '4').save WithTemplateAndUniqueID.new('slug' => '4').save
end end
it "should find first" do it "should find first" do
rs = WithTemplateAndUniqueID.first rs = WithTemplateAndUniqueID.first
rs['important-field'].should == "1" rs['slug'].should == "1"
end end
it "should return nil if no instances are found" do it "should return nil if no instances are found" do
WithTemplateAndUniqueID.all.each {|obj| obj.destroy } WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
@ -370,14 +410,7 @@ describe "Model Base" do
end end
end end
describe "initialization" do describe "recursive validation on a model" do
it "should call after_initialize method if available" do
@doc = WithAfterInitializeMethod.new
@doc['some_value'].should eql('value')
end
end
describe "recursive validation on a model" do
before :each do before :each do
reset_test_db! reset_test_db!
@cat = Cat.new(:name => 'Sockington') @cat = Cat.new(:name => 'Sockington')

View file

@ -202,7 +202,7 @@ describe "Design Documents" do
describe "lazily refreshing the design document" do describe "lazily refreshing the design document" do
before(:all) do before(:all) do
@db = reset_test_db! @db = reset_test_db!
WithTemplateAndUniqueID.new('important-field' => '1').save WithTemplateAndUniqueID.new('slug' => '1').save
end end
it "should not save the design doc twice" do it "should not save the design doc twice" do
WithTemplateAndUniqueID.all WithTemplateAndUniqueID.all

View file

@ -1,40 +1,33 @@
require File.expand_path('../../spec_helper', __FILE__) require File.expand_path('../../spec_helper', __FILE__)
begin class PlainParent
require 'rubygems' unless ENV['SKIP_RUBYGEMS'] class_inheritable_accessor :foo
require 'active_support/json' self.foo = :bar
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 end
class PlainChild < PlainParent
end
class ExtendedParent < CouchRest::Model::Base
class_inheritable_accessor :foo
self.foo = :bar
end
class ExtendedChild < ExtendedParent
end
describe "Using chained inheritance without CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
PlainParent.foo.should == :bar
PlainChild.foo.should == :bar
end
end
describe "Using chained inheritance with CouchRest::Model::Base" do
it "should preserve inheritable attributes" do
ExtendedParent.foo.should == :bar
ExtendedChild.foo.should == :bar
end
end

View file

@ -35,8 +35,8 @@ describe "Model Persistence" do
describe "basic saving and retrieving" do describe "basic saving and retrieving" do
it "should work fine" do it "should work fine" do
@obj.name = "should be easily saved and retrieved" @obj.name = "should be easily saved and retrieved"
@obj.save @obj.save!
saved_obj = WithDefaultValues.get(@obj.id) saved_obj = WithDefaultValues.get!(@obj.id)
saved_obj.should_not be_nil saved_obj.should_not be_nil
end end
@ -223,34 +223,34 @@ describe "Model Persistence" do
it "should require the field" do it "should require the field" do
lambda{@templated.save}.should raise_error lambda{@templated.save}.should raise_error
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
end end
it "should save with the id" do it "should save with the id" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important') t = WithTemplateAndUniqueID.get('very-important')
t.should == @templated t.should == @templated
end end
it "should not change the id on update" do it "should not change the id on update" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
@templated['important-field'] = 'not-important' @templated['slug'] = 'not-important'
@templated.save.should be_true @templated.save.should be_true
t = WithTemplateAndUniqueID.get('very-important') t = WithTemplateAndUniqueID.get('very-important')
t.id.should == @templated.id t.id.should == @templated.id
end end
it "should raise an error when the id is taken" do it "should raise an error when the id is taken" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error lambda{WithTemplateAndUniqueID.new('slug' => 'very-important').save}.should raise_error
end end
it "should set the id" do it "should set the id" do
@templated['important-field'] = 'very-important' @templated['slug'] = 'very-important'
@templated.save.should be_true @templated.save.should be_true
@templated.id.should == 'very-important' @templated.id.should == 'very-important'
end end

View file

@ -315,6 +315,28 @@ describe "a casted model retrieved from the database" do
end end
end end
describe "nested models (not casted)" do
before(:each) do
reset_test_db!
@cat = ChildCat.new(:name => 'Stimpy')
@cat.mother = {:name => 'Stinky'}
@cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
@cat.save
@cat = ChildCat.get(@cat.id)
end
it "should correctly save single relation" do
@cat.mother.name.should eql('Stinky')
@cat.mother.casted_by.should eql(@cat)
end
it "should correctly save collection" do
@cat.siblings.first.name.should eql("Feather")
@cat.siblings.last.casted_by.should eql(@cat)
end
end
describe "Property Class" do describe "Property Class" do
it "should provide name as string" do it "should provide name as string" do

View file

@ -87,7 +87,7 @@ describe "Proxyable" do
DummyProxyable.proxy_for(:cats) DummyProxyable.proxy_for(:cats)
@obj = DummyProxyable.new @obj = DummyProxyable.new
CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true) CouchRest::Model::Proxyable::ModelProxy.should_receive(:new).with(Cat, @obj, 'dummy_proxyable', 'db').and_return(true)
@obj.should_receive('proxy_database').and_return('db') @obj.should_receive(:proxy_database).and_return('db')
@obj.cats @obj.cats
end end
@ -165,15 +165,13 @@ describe "Proxyable" do
end end
it "should proxy new call" do it "should proxy new call" do
Cat.should_receive(:new).and_return({}) @obj.should_receive(:proxy_block_update).with(:new, 'attrs', 'opts')
@obj.should_receive(:proxy_update).and_return(true) @obj.new('attrs', 'opts')
@obj.new
end end
it "should proxy build_from_database" do it "should proxy build_from_database" do
Cat.should_receive(:build_from_database).and_return({}) @obj.should_receive(:proxy_block_update).with(:build_from_database, 'attrs', 'opts')
@obj.should_receive(:proxy_update).with({}).and_return(true) @obj.build_from_database('attrs', 'opts')
@obj.build_from_database
end end
describe "#method_missing" do describe "#method_missing" do
@ -313,6 +311,15 @@ describe "Proxyable" do
@obj.send(:proxy_update_all, docs) @obj.send(:proxy_update_all, docs)
end end
describe "#proxy_block_update" do
it "should proxy block updates" do
doc = { }
@obj.model.should_receive(:new).and_yield(doc)
@obj.should_receive(:proxy_update).with(doc)
@obj.send(:proxy_block_update, :new)
end
end
end end
end end

View file

@ -86,8 +86,9 @@ end
class WithTemplateAndUniqueID < CouchRest::Model::Base class WithTemplateAndUniqueID < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
unique_id do |model| unique_id do |model|
model['important-field'] model.slug
end end
property :slug
property :preset, :default => 'value' property :preset, :default => 'value'
property :has_no_default property :has_no_default
end end

View file

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

View file

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