lib/couchpillow/document.rb in couchpillow-0.3.10 vs lib/couchpillow/document.rb in couchpillow-0.4.0
- old
+ new
@@ -1,48 +1,39 @@
module CouchPillow
class Document
- # Validation Error.
- #
- class ValidationError < StandardError; end
+ extend Attributive
-
- # Faux Boolean class used for type checking and conversion.
- #
- class Boolean; end
-
-
attr_reader :id
+ RESERVED_KEYS = %i[_id _type _created_at _updated_at]
- @type = "default"
+ DEFAULT_TYPE = "default".freeze
- PRESENCE_LAMBDA = lambda do |value| !value.nil? end
+ attribute(:_created_at)
+ .required
+ .type(Time).auto_convert
+ .default { Time.now.utc }
- RESERVED_KEYS = %i[_id _type created_at updated_at]
+ attribute(:_updated_at)
+ .required
+ .type(Time).auto_convert
+ .default { Time.now.utc }
- def initialize hash = {}, id = SecureRandom.hex
+ def initialize hash = {}, id = "#{self.class._type}_#{SecureRandom.hex}"
@data = self.class.symbolize(hash)
+
@id = id
+ time = Time.now.utc
+ @data[:_created_at] ||= time
+ @data[:_updated_at] = time
- @data[:created_at] and
- @data[:created_at].is_a? String and
- @data[:created_at] = Time.parse(@data[:created_at]) or
- @data[:created_at] = Time.now.utc
-
- @data[:updated_at] and
- @data[:updated_at].is_a? String and
- @data[:updated_at] = Time.parse(@data[:updated_at])
-
- raise TypeError if @data[:_type] && @data[:_type] != self.class._type
- @data[:_type] = self.class._type
rename!
whitelist!
- ensure_types!
end
def [] key
@data[key.to_s.to_sym]
@@ -53,90 +44,63 @@
@data[key.to_s.to_sym] = value
end
# @private
- # Map hash keys to methods
#
- def method_missing m, *args, &block
- ms = m.to_s
- if ms.end_with?("=")
- ms.gsub!('=', '')
- @data[ms.to_sym] = args[0]
- else
- if @data.has_key?(m)
- @data[m]
- else
- super
- end
- end
- end
-
-
- # @private
- #
- def respond_to? m
- ms = m.to_s
- return true if ms.end_with?("=")
- @data.has_key?(m) or
- super
- end
-
-
- # @private
- #
def timestamp!
- @data[:updated_at] = Time.now.utc
+ @data[:_updated_at] = Time.now.utc
end
# Save this document to the server
#
def save!
whitelist!
- validate
+ validate!
sort!
timestamp!
- CouchPillow.db.set @id, @data
+ to_save = @data.merge({
+ :_type => self.class._type
+ })
+ CouchPillow.db.set @id, to_save
end
# Delete this document from the server.
#
def delete!
CouchPillow.db.delete @id
end
- # Sort keys on this document.
- #
- def sort!
- @data = @data.sort.to_h
- end
-
-
# Attempt to update this Document. Fails if this Document does not yet
# exist in the database.
#
def update!
whitelist!
- validate
+ validate!
sort!
timestamp!
- CouchPillow.db.replace @id, @data
+ to_save = @data.merge({
+ :_type => self.class._type
+ })
+ CouchPillow.db.replace @id, to_save
end
# Updates the attributes in the document.
# Existing attributes will be overwritten and new ones will be added.
- # Existing attributes that are not present in the hash will be ignored.
+ # Any other existing attributes that are not present in the hash will be ignored.
#
def update hash
hash.each do |k,v|
@data[k.to_sym] = v
end
+ rename!
whitelist!
+ validate!
end
def has? key
@data.has_key?(key)
@@ -147,22 +111,14 @@
to_hash.to_json(*a)
end
def to_hash
- { :_id => @id }.merge!(@data)
+ { :_id => @id, :_type => self.class._type }.merge!(@data)
end
- def validate
- self.class.validate_keys.each do |k, msg, method|
- raise ValidationError, "[#{k}, #{@data[k]}] #{msg}" unless
- @data.has_key?(k) && method.call(@data[k])
- end
- 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
@@ -170,67 +126,65 @@
@data.delete(from)
end
end
- # Ensure types of the values in the Document are converted to the type
- # specified in the {validate_type} method.
+ # Cleanup the @data hash so it only contains relevant fields.
#
- def ensure_types!
- self.class.type_keys.each do |k, t|
- if value = @data[k] and !value.is_a?(t)
- if t == Integer
- @data[k] = Integer(value)
- elsif t == Float
- @data[k] = Float(value)
- elsif t == String
- @data[k] = String(value)
- elsif t == Array
- @data[k] = Array(value)
- elsif t == Time
- @data[k] = Time.parse(value)
- elsif t == Boolean
- @data[k] = value == 0 || !value ? false : true
- end
- end
+ def whitelist!
+ @data.delete_if do |k, v|
+ !self.class.attributes.has_key?(k)
end
end
- # Filter this Document through the whitelist as specified by the
- # {whitelist} directive.
+ # Go through each attribute, and validate the values.
+ # Validation also perform auto-conversion if auto-conversion is enabled
+ # for that attribute.
#
- def whitelist!
- unless self.class.whitelist_keys.empty?
- @data.select! do |k, v|
- RESERVED_KEYS.include?(k) ||
- self.class.whitelist_keys.include?(k)
+ def validate!
+ self.class.attributes.each do |k, attr|
+ if has?(k)
+ @data[k] = attr.validate(@data[k]) if has?(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
end
+ # Sort keys on this document.
+ #
+ 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 Document is of a different type.
+ # @return nil if not found or Document is of a different type.
#
def self.get id
result = CouchPillow.db.get(id) and
- type = result['_type'] and
- type == self._type and
+ type = result[:_type] || result["_type"] and
+ type == _type and
new(result, id) or
nil
end
- # Sets the type of this Document.
- #
- def self.type value
- @type = value.to_s
- end
-
-
# Rename an existing key to a new key. This is invoked right after
# initialize.
#
def self.rename from, to
raise ArgumentError, "Cannot rename reserved keys" if
@@ -238,110 +192,34 @@
rename_keys << [from.to_s.to_sym, to.to_s.to_sym]
end
- # Validate the presence of a particular key, and the value of that key
- # cannot be nil.
+ # Sets the type of this Document.
#
- def self.validate_presence key
- validate key, "is missing", PRESENCE_LAMBDA
+ def self.type value
+ @type = value.to_s
end
- # Validate the type of a particular key.
- #
- # @example
- # validate_type :name, String
- #
- def self.validate_type key, type
- validate key, "#{key} is not the correct type. Expected a #{type}", lambda { |v| v.is_a? type }
- type_keys << [key, type]
- end
-
-
- # Validate the type is a Boolean, since Ruby lacks a Boolean class.
- #
- # @example
- # validate_type_boolean :available, Boolean
- #
- def self.validate_type_boolean key
- validate key, "#{key} is not the correct type. Expected a Boolean", lambda { |v| !!v == v }
- type_keys << [key, Boolean]
- end
-
-
- # Validate the presence of a particular key using a custom validation method.
- #
- # @param key Key to be validated.
- # @param message Message that will be displayed when validation fails.
- # @yield [v] Value of the key.
- # The block must return truthy for it to pass the validation.
- #
- # @example
- # validate :first_name, 'first name is not Joe', lambda { |v| v != "Joe" }
- #
- def self.validate key, message, block
- raise ValidationError, "Provide validation method for key #{key}" unless block
- validate_keys << [key, message, block]
- end
-
-
- # This Document should only accept keys that are specified here.
- # The existence of these keys are optional, and won't trigger any validation
- # unless specified by the validate methods. Hashes passed to {#update} and
- # {#initialize} will be filtered through this list.
- #
- # If you don't specify a whitelist, Document will accept any keys, but
- # once you specify it, only those keys will be accepted and the rest will
- # be dropped.
- #
- # @param list Whitelist of keys.
- #
- # @example
- # whitelist :first_name, :last_name, :address
- #
- def self.whitelist *list
- list.flatten.each do |k|
- whitelist_keys << k.to_s.to_sym
- end
- end
-
-
private
- # Read the type of this Document. Internal use only.
- #
def self._type
- @type
+ @type ||= DEFAULT_TYPE
end
def self.rename_keys
@rename_keys ||= []
end
- def self.type_keys
- @type_keys ||= []
- end
-
-
- def self.validate_keys
- @validate_keys ||= []
- end
-
-
- def self.whitelist_keys
- @whitelist_keys ||= []
- end
-
-
def self.symbolize hash
hash.inject({}) do |memo,(k,v)|
memo[k.to_sym] = v
memo
end
end
+
end
end