lib/couchpillow/document.rb in couchpillow-0.4.3 vs lib/couchpillow/document.rb in couchpillow-0.4.4

- old
+ new

@@ -6,32 +6,38 @@ attr_reader :id RESERVED_KEYS = %i[_id _type _created_at _updated_at] - DEFAULT_TYPE = "default".freeze + DEFAULT_TYPE = "couchpillow".freeze - attribute(:_created_at) - .required - .type(Time).auto_convert - .default { Time.now.utc } + attribute :_created_at do + required + type Time + auto_convert + default { Time.now.utc } + end - attribute(:_updated_at) - .required - .type(Time).auto_convert - .default { Time.now.utc } + attribute :_updated_at do + required + type Time + auto_convert + default { Time.now.utc } + end - def initialize hash = {}, id = "#{self.class._type}::#{SecureRandom.hex}" + def initialize hash = {}, id = "#{self.class.doc_type}::#{SecureRandom.hex}" @data = self.class.symbolize(hash) @id = id time = Time.now.utc @data[:_created_at] ||= time @data[:_updated_at] = time + @futures = [] + rename! whitelist! assign_defaults! auto_convert! end @@ -45,35 +51,50 @@ def []= key, value @data[key.to_s.to_sym] = value end - # @private - # - def timestamp! - @data[:_updated_at] = Time.now.utc - end - - # Save this document to the server # def save! opts = {} whitelist! sort! timestamp! validate! to_save = @data.merge({ - :_type => self.class._type + :_type => self.class.doc_type }) - CouchPillow.db.set(@id, to_save, opts) + + # write to all connections + result = self.class.default_db.set(@id, to_save, opts) + + unless self.class.secondary_dbs.empty? + @futures << Celluloid::Future.new do + self.class.secondary_dbs.each do |db| + db.set(@id, to_save, opts) + end + end + end + + result end # Delete this document from the server. # def delete! - CouchPillow.db.delete @id + result = self.class.default_db.delete @id + + unless self.class.secondary_dbs.empty? + @futures << Celluloid::Future.new do + self.class.secondary_dbs.each do |db| + db.delete @id + end + end + end + + result end # Attempt to update this Document. Fails if this Document does not yet # exist in the database. @@ -82,13 +103,24 @@ whitelist! sort! timestamp! validate! to_save = @data.merge({ - :_type => self.class._type + :_type => self.class.doc_type }) - CouchPillow.db.replace @id, to_save + + result = self.class.default_db.replace @id, to_save + + unless self.class.secondary_dbs.empty? + @futures << Celluloid::Future.new do + self.class.secondary_dbs.each do |db| + db.replace @id, to_save + end + end + end + + result end # Updates the attributes in the document. # Existing attributes will be overwritten and new ones will be added. @@ -98,28 +130,57 @@ hash.each do |k,v| @data[k.to_sym] = v end rename! whitelist! + auto_convert! end + # Check if this Document has the key + # def has? key @data.has_key?(key) end + # Convert this Document to a JSON string + # def to_json *a to_hash.to_json(*a) end + # Convert this Document to a Hash + # def to_hash - { :_id => @id, :_type => self.class._type }.merge!(@data) + { :_id => @id, :_type => self.class.doc_type }.merge!(@data) end + # Helper to get the type of this Document. + # Can't really name this `type`. Need to avoid name conflict with Ruby's own `type` method. + # + def doc_type + self.class.doc_type + end + + + # Blocks until all pending tasks has completed. + # Returns the result of those tasks in an array. + # + def wait + result = [] + until @futures.empty? + f = @futures.shift + result << f.value + end + + result + end + + # Rename the keys in this Document as specified by the {rename} directive. # def rename! self.class.rename_keys.each do |from, to| @data.has_key?(from) and @@ -136,11 +197,11 @@ !self.class.attributes.has_key?(k) end end - # Assign default value + # Assign default values. # def assign_defaults! self.class.attributes.each do |k, attr| @data[k] = attr.trigger_default_directive if !has?(k) && attr.has_default? end @@ -155,17 +216,16 @@ end end # Go through each attribute, and validate the values. - # Validation also perform auto-conversion if auto-conversion is enabled - # for that attribute. + # Validation also perform auto-conversion if auto-conversion is enabled for that attribute. # def validate! self.class.attributes.each do |k, attr| if has?(k) - @data[k] = attr.validate(@data[k]) if has?(k) + @data[k] = attr.validate(@data[k]) else @data[k] = attr.trigger_default_directive if attr.has_default? raise ValidationError, "Attribute '#{k}' is required" if attr.required? && !has?(k) end end @@ -177,28 +237,18 @@ def sort! @data = @data.sort.to_h end - def _type - self.class._type - end - - - def _id - @id - end - - # Get a Document given an id. # # @return nil if not found or Document is of a different type. # def self.get id - result = CouchPillow.db.get(id) and + result = default_db.get(id) and type = result[:_type] || result["_type"] and - type == _type and + type == doc_type and new(result, id) or nil end @@ -218,19 +268,56 @@ def self.type value @type = value.to_s end + # Set a DB connection. Overrides the default CouchPillow.db connection + # for the first time this method gets called. Subsequent calls will set + # secondary connections, which will only be used for write only. + # + # Example: + # db primary_db # use for both read and write + # db backup_db1 # write only + # db backup_db2 # write only + # + def self.db conn + # set the primary db connection + @primary_db ||= conn + + # insert as backup db connections + if conn && @primary_db && conn != @primary_db + secondary_dbs << conn + end + end + + private - def self._type + # Timestamp this document + # + def timestamp! + @data[:_updated_at] = Time.now.utc + end + + + def self.doc_type @type ||= DEFAULT_TYPE end def self.rename_keys @rename_keys ||= [] + end + + + def self.default_db + @default_db ||= (@primary_db || CouchPillow.db) + end + + + def self.secondary_dbs + @secondary_dbs ||= [] end def self.symbolize hash hash.inject({}) do |memo,(k,v)|