now using ActiveModel::Dirty. only writes to database if model.changed?
This commit is contained in:
parent
53b052f631
commit
4dbf694e51
14 changed files with 342 additions and 18 deletions
|
@ -16,6 +16,7 @@ module CouchRest
|
|||
include CouchRest::Model::PropertyProtection
|
||||
include CouchRest::Model::Associations
|
||||
include CouchRest::Model::Validations
|
||||
include CouchRest::Model::Dirty
|
||||
|
||||
def self.subclasses
|
||||
@subclasses ||= []
|
||||
|
@ -55,7 +56,7 @@ module CouchRest
|
|||
after_initialize if respond_to?(:after_initialize)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Temp solution to make the view_by methods available
|
||||
def self.method_missing(m, *args, &block)
|
||||
if has_view?(m)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
module CouchRest::Model
|
||||
class CastedArray < Array
|
||||
include CouchRest::Model::Dirty
|
||||
attr_accessor :casted_by
|
||||
attr_accessor :property
|
||||
|
||||
|
@ -14,15 +15,34 @@ module CouchRest::Model
|
|||
end
|
||||
|
||||
def << obj
|
||||
couchrest_parent_will_change!
|
||||
super(instantiate_and_cast(obj))
|
||||
end
|
||||
|
||||
def push(obj)
|
||||
couchrest_parent_will_change!
|
||||
super(instantiate_and_cast(obj))
|
||||
end
|
||||
|
||||
def pop
|
||||
couchrest_parent_will_change!
|
||||
super
|
||||
end
|
||||
|
||||
def shift
|
||||
couchrest_parent_will_change!
|
||||
super
|
||||
end
|
||||
|
||||
def unshift(obj)
|
||||
couchrest_parent_will_change!
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def []= index, obj
|
||||
super(index, instantiate_and_cast(obj))
|
||||
value = instantiate_and_cast(obj)
|
||||
couchrest_parent_will_change! if value != self[index]
|
||||
super(index, value)
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
20
lib/couchrest/model/casted_hash.rb
Normal file
20
lib/couchrest/model/casted_hash.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
# Wrapper around Hash so that the casted_by attribute is set.
|
||||
|
||||
module CouchRest::Model
|
||||
class CastedHash < Hash
|
||||
include CouchRest::Model::Dirty
|
||||
attr_accessor :casted_by
|
||||
|
||||
def []= index, obj
|
||||
couchrest_parent_will_change! if obj != self[index]
|
||||
super(index, obj)
|
||||
end
|
||||
|
||||
# needed for dirty
|
||||
def attributes
|
||||
self
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -10,6 +10,7 @@ module CouchRest::Model
|
|||
include CouchRest::Model::PropertyProtection
|
||||
include CouchRest::Model::Associations
|
||||
include CouchRest::Model::Validations
|
||||
include CouchRest::Model::Dirty
|
||||
attr_accessor :casted_by
|
||||
end
|
||||
|
||||
|
@ -20,6 +21,7 @@ module CouchRest::Model
|
|||
end
|
||||
|
||||
def []= key, value
|
||||
couchrest_attribute_will_change!(key) unless self[key] == value
|
||||
super(key.to_s, value)
|
||||
end
|
||||
|
||||
|
@ -64,5 +66,6 @@ module CouchRest::Model
|
|||
end
|
||||
end
|
||||
alias :attributes= :update_attributes_without_saving
|
||||
|
||||
end
|
||||
end
|
||||
|
|
41
lib/couchrest/model/dirty.rb
Normal file
41
lib/couchrest/model/dirty.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# encoding: utf-8
|
||||
|
||||
I18n.load_path << File.join(
|
||||
File.dirname(__FILE__), "validations", "locale", "en.yml"
|
||||
)
|
||||
|
||||
module CouchRest
|
||||
module Model
|
||||
|
||||
# This applies to both Model::Base and Model::CastedModel
|
||||
module Dirty
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActiveModel::Dirty
|
||||
end
|
||||
|
||||
def couchrest_attribute_will_change!(attr)
|
||||
return if attr.nil?
|
||||
self.send("#{attr}_will_change!")
|
||||
if pkey = casted_by_attribute
|
||||
@casted_by.couchrest_attribute_will_change!(pkey)
|
||||
end
|
||||
end
|
||||
|
||||
def couchrest_parent_will_change!
|
||||
@casted_by.couchrest_attribute_will_change!(casted_by_attribute) if @casted_by
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# return the attribute name this object is referenced by in the parent
|
||||
def casted_by_attribute
|
||||
return nil unless @casted_by
|
||||
attr = @casted_by.attributes
|
||||
attr.keys.detect { |k| attr[k] == self }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,12 @@
|
|||
module CouchRest
|
||||
module Model
|
||||
module ExtendedAttachments
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Dirty
|
||||
included do
|
||||
# for _attachments_will_change!
|
||||
define_attribute_methods [:_attachments]
|
||||
end
|
||||
|
||||
# Add a file attachment to the current document. Expects
|
||||
# :file and :name to be included in the arguments.
|
||||
|
@ -35,6 +41,7 @@ module CouchRest
|
|||
# deletes a file attachment from the current doc
|
||||
def delete_attachment(attachment_name)
|
||||
return unless attachments
|
||||
_attachments_will_change! if attachments.include?(attachment_name)
|
||||
attachments.delete attachment_name
|
||||
end
|
||||
|
||||
|
@ -66,6 +73,8 @@ module CouchRest
|
|||
def set_attachment_attr(args)
|
||||
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file].path)
|
||||
content_type ||= (get_mime_type(args[:name]) || 'text/plain')
|
||||
|
||||
_attachments_will_change!
|
||||
attachments[args[:name]] = {
|
||||
'content_type' => content_type,
|
||||
'data' => args[:file].read
|
||||
|
|
|
@ -12,7 +12,9 @@ module CouchRest
|
|||
_run_save_callbacks do
|
||||
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
||||
result = database.save_doc(self)
|
||||
(result["ok"] == true) ? self : false
|
||||
ret = (result["ok"] == true) ? self : false
|
||||
@changed_attributes.clear if ret && @changed_attributes
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -28,10 +30,13 @@ module CouchRest
|
|||
def update(options = {})
|
||||
raise "Calling #{self.class.name}#update on document that has not been created!" if self.new?
|
||||
return false unless perform_validations(options)
|
||||
return true unless self.changed?
|
||||
_run_update_callbacks do
|
||||
_run_save_callbacks do
|
||||
result = database.save_doc(self)
|
||||
result["ok"] == true
|
||||
ret = result["ok"] == true
|
||||
@changed_attributes.clear if ret && @changed_attributes
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -140,12 +145,18 @@ module CouchRest
|
|||
# should use the class name as part of the unique id.
|
||||
def unique_id method = nil, &block
|
||||
if method
|
||||
define_method :get_unique_id do
|
||||
self.send(method)
|
||||
end
|
||||
define_method :set_unique_id do
|
||||
self['_id'] ||= self.send(method)
|
||||
self['_id'] ||= get_unique_id
|
||||
end
|
||||
elsif block
|
||||
define_method :get_unique_id do
|
||||
block.call(self)
|
||||
end
|
||||
define_method :set_unique_id do
|
||||
uniqid = block.call(self)
|
||||
uniqid = get_unique_id
|
||||
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
||||
self['_id'] ||= uniqid
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@ module CouchRest
|
|||
module Model
|
||||
module Properties
|
||||
extend ActiveSupport::Concern
|
||||
include ActiveModel::Dirty
|
||||
|
||||
included do
|
||||
extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
|
||||
|
@ -38,11 +39,33 @@ module CouchRest
|
|||
|
||||
# Store a casted value in the current instance of an attribute defined
|
||||
# with a property.
|
||||
# TODO: mixin dirty functionality into value (?)
|
||||
def write_attribute(property, value)
|
||||
prop = find_property!(property)
|
||||
self[prop.to_s] = prop.is_a?(String) ? value : prop.cast(self, value)
|
||||
end
|
||||
|
||||
# write property, update dirty status
|
||||
def write_attribute_dirty(property, value)
|
||||
prop = find_property!(property)
|
||||
value = prop.is_a?(String) ? value : prop.cast(self, value)
|
||||
self.send("#{prop}_will_change!") unless self[prop.to_s] == value
|
||||
write_attribute(property, value)
|
||||
end
|
||||
|
||||
def []=(key,value)
|
||||
old_id = get_unique_id if self.respond_to?(:get_unique_id)
|
||||
|
||||
super(key, value)
|
||||
|
||||
if self.respond_to?(:get_unique_id)
|
||||
# if we have set an attribute that results in the _id changing (unique_id),
|
||||
# force changed? to return true so that the record can be saved
|
||||
new_id = get_unique_id
|
||||
changed_attributes["_id"] = new_id if old_id != new_id
|
||||
end
|
||||
end
|
||||
|
||||
# Takes a hash as argument, and applies the values by using writer methods
|
||||
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
||||
# missing. In case of error, no attributes are changed.
|
||||
|
@ -50,12 +73,23 @@ module CouchRest
|
|||
# Remove any protected and update all the rest. Any attributes
|
||||
# which do not have a property will simply be ignored.
|
||||
attrs = remove_protected_attributes(hash)
|
||||
directly_set_attributes(attrs)
|
||||
directly_set_attributes(attrs, :dirty => true)
|
||||
end
|
||||
alias :attributes= :update_attributes_without_saving
|
||||
|
||||
# needed for Dirty
|
||||
def attributes
|
||||
ret = {}
|
||||
self.class.properties.each do |property|
|
||||
ret[property.name] = read_attribute(property)
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def find_property(property)
|
||||
property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
||||
end
|
||||
|
||||
private
|
||||
# The following methods should be accessable by the Model::Base Class, but not by anything else!
|
||||
def apply_all_property_defaults
|
||||
return if self.respond_to?(:new?) && (new? == false)
|
||||
|
@ -76,17 +110,26 @@ module CouchRest
|
|||
end
|
||||
|
||||
def find_property!(property)
|
||||
prop = property.is_a?(Property) ? property : self.class.properties.detect {|p| p.to_s == property.to_s}
|
||||
prop = find_property(property)
|
||||
raise ArgumentError, "Missing property definition for #{property.to_s}" if prop.nil?
|
||||
prop
|
||||
end
|
||||
|
||||
# Set all the attributes and return a hash with the attributes
|
||||
# that have not been accepted.
|
||||
def directly_set_attributes(hash)
|
||||
def directly_set_attributes(hash, options = {})
|
||||
hash.reject do |attribute_name, attribute_value|
|
||||
if self.respond_to?("#{attribute_name}=")
|
||||
self.send("#{attribute_name}=", attribute_value)
|
||||
if find_property(attribute_name)
|
||||
if options[:dirty]
|
||||
self.write_attribute_dirty(attribute_name, attribute_value)
|
||||
else
|
||||
# set attribute without updating dirty status
|
||||
self.write_attribute(attribute_name, attribute_value)
|
||||
end
|
||||
else
|
||||
self.send("#{attribute_name}=", attribute_value)
|
||||
end
|
||||
true
|
||||
elsif mass_assign_any_attribute # config option
|
||||
self[attribute_name] = attribute_value
|
||||
|
@ -126,7 +169,7 @@ module CouchRest
|
|||
end
|
||||
existing_property = self.properties.find{|p| p.name == name.to_s}
|
||||
if existing_property.nil? || (existing_property.default != opts[:default])
|
||||
define_property(name, opts, &block)
|
||||
define_property(name, opts, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -139,8 +182,8 @@ module CouchRest
|
|||
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?
|
||||
write_attribute_dirty('updated_at', Time.now)
|
||||
write_attribute_dirty('created_at', Time.now) if object.new?
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -203,7 +246,7 @@ module CouchRest
|
|||
property_name = property.name
|
||||
class_eval <<-EOS
|
||||
def #{property_name}=(value)
|
||||
write_attribute('#{property_name}', value)
|
||||
write_attribute_dirty('#{property_name}', value)
|
||||
end
|
||||
EOS
|
||||
|
||||
|
|
|
@ -40,6 +40,10 @@ module CouchRest::Model
|
|||
# allow casted_by calls to be passed up chain by wrapping in CastedArray
|
||||
value = type_class != String ? CastedArray.new(arr, self) : arr
|
||||
value.casted_by = parent if value.respond_to?(:casted_by)
|
||||
elsif (type == Object || type == Hash) && (value.class == Hash)
|
||||
# allow casted_by calls to be passed up chain by wrapping in CastedHash
|
||||
value = CouchRest::Model::CastedHash[value]
|
||||
value.casted_by = parent
|
||||
elsif !value.nil?
|
||||
value = cast_value(parent, value)
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue