implemented some missing dirty functionality for casted_array and casted_hash. improved dirty spec test

This commit is contained in:
Andrew Williams 2011-03-03 23:22:19 +10:30
parent dcf43e3641
commit 2a9305ebd3
8 changed files with 284 additions and 35 deletions

View file

@ -33,6 +33,10 @@ def set_dirty(value)
orig orig
end end
def supports_dirty?
CouchRest::Model::Base.respond_to?(:use_dirty)
end
def run_benchmark def run_benchmark
n = 50000 # property operation count n = 50000 # property operation count
db_n = 1000 # database operation count db_n = 1000 # database operation count
@ -97,11 +101,13 @@ def run_benchmark
end end
begin begin
puts "with use_dirty true" if supports_dirty?
set_dirty(true) puts "with use_dirty true"
run_benchmark set_dirty(true)
run_benchmark
puts "\nwith use_dirty false" puts "\nwith use_dirty false"
set_dirty(false) set_dirty(false)
end
run_benchmark run_benchmark
end end

View file

@ -15,36 +15,41 @@ module CouchRest::Model
end end
def << obj def << obj
couchrest_parent_will_change! couchrest_parent_will_change! if use_dirty?
super(instantiate_and_cast(obj)) super(instantiate_and_cast(obj))
end end
def push(obj) def push(obj)
couchrest_parent_will_change! couchrest_parent_will_change! if use_dirty?
super(instantiate_and_cast(obj)) super(instantiate_and_cast(obj))
end end
def pop def pop
couchrest_parent_will_change! couchrest_parent_will_change! if use_dirty? && self.length > 0
super super
end end
def shift def shift
couchrest_parent_will_change! couchrest_parent_will_change! if use_dirty? && self.length > 0
super super
end end
def unshift(obj) def unshift(obj)
couchrest_parent_will_change! couchrest_parent_will_change! if use_dirty?
super(obj) super(instantiate_and_cast(obj))
end end
def []= index, obj def []= index, obj
value = instantiate_and_cast(obj) value = instantiate_and_cast(obj)
couchrest_parent_will_change! if value != self[index] couchrest_parent_will_change! if use_dirty? && value != self[index]
super(index, value) super(index, value)
end end
def clear
couchrest_parent_will_change! if use_dirty? && self.length > 0
super
end
protected protected
def instantiate_and_cast(obj) def instantiate_and_cast(obj)

View file

@ -6,16 +6,70 @@ module CouchRest::Model
include CouchRest::Model::Dirty include CouchRest::Model::Dirty
attr_accessor :casted_by attr_accessor :casted_by
def []= index, obj
return super(index, obj) unless use_dirty?
couchrest_parent_will_change! if obj != self[index]
super(index, obj)
end
# needed for dirty # needed for dirty
def attributes def attributes
self self
end end
def []= key, obj
couchrest_attribute_will_change!(key) if use_dirty? && obj != self[key]
super(key, obj)
end
def delete(key)
couchrest_attribute_will_change!(key) if use_dirty? && include?(key)
super(key)
end
def merge!(other_hash)
if use_dirty? && other_hash && other_hash.kind_of?(Hash)
other_hash.keys.each do |key|
if self[key] != other_hash[key] || !include?(key)
couchrest_attribute_will_change!(key)
end
end
end
super(other_hash)
end
def replace(other_hash)
if use_dirty? && other_hash && other_hash.kind_of?(Hash)
# new keys and changed keys
other_hash.keys.each do |key|
if self[key] != other_hash[key] || !include?(key)
couchrest_attribute_will_change!(key)
end
end
# old keys
old_keys = self.keys.reject { |key| other_hash.include?(key) }
old_keys.each { |key| couchrest_attribute_will_change!(key) }
end
super(other_hash)
end
def clear
self.keys.each { |key| couchrest_attribute_will_change!(key) } if use_dirty?
super
end
def delete_if
if use_dirty? && block_given?
self.keys.each do |key|
couchrest_attribute_will_change!(key) if yield key, self[key]
end
end
super
end
def keep_if
if use_dirty? && block_given?
self.keys.each do |key|
couchrest_attribute_will_change!(key) if !yield key, self[key]
end
end
super
end
end end
end end

View file

@ -13,11 +13,6 @@ module CouchRest
include CouchRest::Model::CastedBy # needed for base_doc include CouchRest::Model::CastedBy # needed for base_doc
include ActiveModel::Dirty include ActiveModel::Dirty
def use_dirty?
bdoc = base_doc
bdoc && !bdoc.disable_dirty && bdoc.use_dirty
end
included do included do
# internal dirty setting - overrides global setting. # internal dirty setting - overrides global setting.
# this is used to temporarily disable dirty tracking when setting # this is used to temporarily disable dirty tracking when setting
@ -25,6 +20,11 @@ module CouchRest
self.send(:attr_accessor, :disable_dirty) self.send(:attr_accessor, :disable_dirty)
end end
def use_dirty?
bdoc = base_doc
bdoc && !bdoc.disable_dirty && bdoc.use_dirty
end
def couchrest_attribute_will_change!(attr) def couchrest_attribute_will_change!(attr)
return if attr.nil? || !use_dirty? return if attr.nil? || !use_dirty?
attribute_will_change!(attr) attribute_will_change!(attr)

View file

@ -201,7 +201,7 @@ module CouchRest
end end
type = [type] # inject as an array type = [type] # inject as an array
end end
property = Property.new(name, type, options) property = Property.new(name, type, options.merge(:use_dirty => use_dirty))
create_property_getter(property) create_property_getter(property)
create_property_setter(property) unless property.read_only == true create_property_setter(property) unless property.read_only == true
if property.type_class.respond_to?(:validates_casted_model) if property.type_class.respond_to?(:validates_casted_model)

View file

@ -4,7 +4,7 @@ module CouchRest::Model
include ::CouchRest::Model::Typecast include ::CouchRest::Model::Typecast
attr_reader :name, :type, :type_class, :read_only, :alias, :default, :casted, :init_method, :options attr_reader :name, :type, :type_class, :read_only, :alias, :default, :casted, :init_method, :use_dirty, :options
# Attribute to define. # Attribute to define.
# All Properties are assumed casted unless the type is nil. # All Properties are assumed casted unless the type is nil.
@ -38,7 +38,7 @@ module CouchRest::Model
end end
arr = value.collect { |data| cast_value(parent, data) } arr = value.collect { |data| cast_value(parent, data) }
# allow casted_by calls to be passed up chain by wrapping in CastedArray # allow casted_by calls to be passed up chain by wrapping in CastedArray
value = type_class != String ? CastedArray.new(arr, self) : arr value = (use_dirty || type_class != String) ? CastedArray.new(arr, self) : arr
value.casted_by = parent if value.respond_to?(:casted_by) value.casted_by = parent if value.respond_to?(:casted_by)
elsif (type == Object || type == Hash) && (value.class == Hash) elsif (type == Object || type == Hash) && (value.class == 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
@ -94,6 +94,7 @@ module CouchRest::Model
@alias = options.delete(:alias) if options[:alias] @alias = options.delete(:alias) if options[:alias]
@default = options.delete(:default) unless options[:default].nil? @default = options.delete(:default) unless options[:default].nil?
@init_method = options[:init_method] ? options.delete(:init_method) : 'new' @init_method = options[:init_method] ? options.delete(:init_method) : 'new'
@use_dirty = options.delete(:use_dirty)
@options = options @options = options
end end

View file

@ -160,7 +160,7 @@ describe CouchRest::Model::CastedModel do
end end
it "should cast the array properly" do it "should cast the array properly" do
@obj.keywords.should be_an_instance_of(Array) @obj.keywords.should be_kind_of(Array)
@obj.keywords.first.should == 'couch' @obj.keywords.first.should == 'couch'
end end
end end

View file

@ -17,8 +17,8 @@ class DummyModel < CouchRest::Model::Base
use_database TEST_SERVER.default_database use_database TEST_SERVER.default_database
raise "Default DB not set" if TEST_SERVER.default_database.nil? raise "Default DB not set" if TEST_SERVER.default_database.nil?
property :casted_attribute, WithCastedModelMixin property :casted_attribute, WithCastedModelMixin
property :details, Object, :default => {} property :details, Object, :default => { 'color' => 'blue' }
property :keywords, [String] property :keywords, [String], :default => ['default-keyword']
property :sub_models do |child| property :sub_models do |child|
child.property :title child.property :title
end end
@ -128,6 +128,11 @@ describe "With use_dirty(on)" do
@card.changed?.should be_false @card.changed?.should be_false
end end
it "should report no changes on a hash property with a default value" do
@obj = DummyModel.new
@obj.details.changed?.should be_false
end
=begin =begin
# match activerecord behaviour # match activerecord behaviour
# not currently working - not too important # not currently working - not too important
@ -218,12 +223,190 @@ describe "With use_dirty(on)" do
@cat.changed?.should be_true @cat.changed?.should be_true
end end
it "should report changes to hashes" do # casted arrays
@obj = DummyModel.create!
@obj = DummyModel.get(@obj.id) def test_casted_array(change_expected)
deets = @obj.details obj = DummyModel.create!
deets['color'] = 'orange' obj = DummyModel.get(obj.id)
@obj.changed?.should be_true array = obj.keywords
yield array, obj
if change_expected
obj.changed?.should be_true
else
obj.changed?.should be_false
end
end
def should_change_array
test_casted_array(true) { |a,b| yield a,b }
end
def should_not_change_array
test_casted_array(false) { |a,b| yield a,b }
end
it "should report changes if an array index is modified" do
should_change_array do |array|
array[0] = "keyword"
end
end
it "should report no changes if an array index is unmodified" do
should_not_change_array do |array|
array[0] = array[0]
end
end
it "should report changes if an array is appended with <<" do
should_change_array do |array|
array << 'keyword'
end
end
it "should report changes if an array is popped" do
should_change_array do |array|
array.pop
end
end
it "should report no changes if an empty array is popped" do
should_not_change_array do |array, obj|
array.clear
obj.save! # clears changes
array.pop
end
end
it "should report changes if an array is pushed" do
should_change_array do |array|
array.push("keyword")
end
end
it "should report changes if an array is shifted" do
should_change_array do |array|
array.shift
end
end
it "should report no changes if an empty array is shifted" do
should_not_change_array do |array, obj|
array.clear
obj.save! # clears changes
array.shift
end
end
it "should report changes if an array is unshifted" do
should_change_array do |array|
array.unshift("keyword")
end
end
it "should report changes if an array is cleared" do
should_change_array do |array|
array.clear
end
end
# Object, {} (casted hash)
def test_casted_hash(change_expected)
obj = DummyModel.create!
obj = DummyModel.get(obj.id)
hash = obj.details
yield hash, obj
if change_expected
obj.changed?.should be_true
else
obj.changed?.should be_false
end
end
def should_change_hash
test_casted_hash(true) { |a,b| yield a,b }
end
def should_not_change_hash
test_casted_hash(false) { |a,b| yield a,b }
end
it "should report changes if a hash is modified" do
should_change_hash do |hash|
hash['color'] = 'orange'
end
end
it "should report no changes if a hash is unmodified" do
should_not_change_hash do |hash|
hash['color'] = hash['color']
end
end
it "should report changes when deleting from a hash" do
should_change_hash do |hash|
hash.delete('color')
end
end
it "should report no changes when deleting a non existent key from a hash" do
should_not_change_hash do |hash|
hash.delete('non-existent-key')
end
end
it "should report changes when clearing a hash" do
should_change_hash do |hash|
hash.clear
end
end
it "should report changes when merging changes to a hash" do
should_change_hash do |hash|
hash.merge!('foo' => 'bar')
end
end
it "should report no changes when merging no changes to a hash" do
should_not_change_hash do |hash|
hash.merge!('color' => hash['color'])
end
end
it "should report changes when replacing hash content" do
should_change_hash do |hash|
hash.replace('foo' => 'bar')
end
end
it "should report no changes when replacing hash content with same content" do
should_not_change_hash do |hash|
hash.replace(hash)
end
end
it "should report changes when removing records with delete_if" do
should_change_hash do |hash|
hash.delete_if { true }
end
end
it "should report no changes when removing no records with delete_if" do
should_not_change_hash do |hash|
hash.delete_if { false }
end
end
it "should report changes when removing records with keep_if" do
should_change_hash do |hash|
hash.keep_if { false }
end
end
it "should report no changes when removing no records with keep_if" do
should_not_change_hash do |hash|
hash.keep_if { true }
end
end end
end end