lib/dynamoid/document.rb in dynamoid-3.5.0 vs lib/dynamoid/document.rb in dynamoid-3.6.0
- old
+ new
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-module Dynamoid #:nodoc:
+module Dynamoid
# This is the base module for all domain objects that need to be persisted to
# the database as documents.
module Document
extend ActiveSupport::Concern
include Dynamoid::Components
@@ -15,100 +15,145 @@
Dynamoid.included_models << self unless Dynamoid.included_models.include? self
end
module ClassMethods
- # Set up table options, including naming it whatever you want, setting the id key, and manually overriding read and
- # write capacity.
- #
- # @param [Hash] options options to pass for this table
- # @option options [Symbol] :name the name for the table; this still gets namespaced
- # @option options [Symbol] :id id column for the table
- # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
- # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
- #
- # @since 0.4.0
+ # @private
def table(options = {})
self.options = options
super if defined? super
end
def attr_readonly(*read_only_attributes)
- ActiveSupport::Deprecation.warn('[Dynamoid] .attr_readonly is deprecated! Call .find instead of')
self.read_only_attributes.concat read_only_attributes.map(&:to_s)
end
- # Returns the read_capacity for this table.
+ # Returns the read capacity for this table.
#
+ # @return [Integer] read capacity units
# @since 0.4.0
def read_capacity
options[:read_capacity] || Dynamoid::Config.read_capacity
end
# Returns the write_capacity for this table.
#
+ # @return [Integer] write capacity units
# @since 0.4.0
def write_capacity
options[:write_capacity] || Dynamoid::Config.write_capacity
end
# Returns the billing (capacity) mode for this table.
- # Could be either :provisioned or :on_demand
+ #
+ # Could be either +provisioned+ or +on_demand+.
+ #
+ # @return [Symbol]
def capacity_mode
options[:capacity_mode] || Dynamoid::Config.capacity_mode
end
# Returns the field name used to support STI for this table.
+ #
+ # Default field name is +type+ but it can be overrided in the +table+
+ # method call.
+ #
+ # User.inheritance_field # => :type
def inheritance_field
options[:inheritance_field] || :type
end
- # Returns the id field for this class.
+ # Returns the hash key field name for this class.
#
+ # By default +id+ field is used. But it can be overriden in the +table+
+ # method call.
+ #
+ # User.hash_key # => :id
+ #
+ # @return [Symbol] a hash key name
# @since 0.4.0
def hash_key
options[:key] || :id
end
- # Returns the number of items for this class.
+ # Return the count of items for this class.
#
+ # It returns aproximate value based on DynamoDB statistic. DynamoDB
+ # updates it periodicaly so the value can be no accurate.
+ #
+ # It's a reletivly cheap operation and doesn't read all the items in a
+ # table. It makes just one HTTP request to DynamoDB.
+ #
+ # @return [Integer] items count in a table
# @since 0.6.1
def count
Dynamoid.adapter.count(table_name)
end
# Initialize a new object.
#
- # @param [Hash] attrs Attributes with which to create the object.
+ # User.build(name: 'A')
#
- # @return [Dynamoid::Document] the new document
+ # Initialize an object and pass it into a block to set other attributes.
#
+ # User.build(name: 'A') do |u|
+ # u.age = 21
+ # end
+ #
+ # The only difference between +build+ and +new+ methods is that +build+
+ # supports STI (Single table inheritance) and looks at the inheritance
+ # field. So it can build a model of actual class. For instance:
+ #
+ # class Employee
+ # include Dynamoid::Document
+ #
+ # field :type
+ # field :name
+ # end
+ #
+ # class Manager < Employee
+ # end
+ #
+ # Employee.build(name: 'Alice', type: 'Manager') # => #<Manager:0x00007f945756e3f0 ...>
+ #
+ # @param attrs [Hash] Attributes with which to create the document
+ # @param block [Proc] Block to process a document after initialization
+ # @return [Dynamoid::Document] the new document
# @since 0.2.0
- def build(attrs = {})
- choose_right_class(attrs).new(attrs)
+ def build(attrs = {}, &block)
+ choose_right_class(attrs).new(attrs, &block)
end
- # Does this object exist?
+ # Does this model exist in a table?
#
- # Supports primary key in format that `find` call understands.
- # Multiple keys and single compound primary key should be passed only as Array explicitily.
+ # User.exists?('713') # => true
#
- # Supports conditions in format that `where` call understands.
+ # If a range key is declared it should be specified in the following way:
#
- # @param [Mixed] id_or_conditions the id of the object or a hash with the options to filter from.
+ # User.exists?([['713', 'range-key-value']]) # => true
#
- # @return [Boolean] true/false
+ # It's possible to check existence of several models at once:
#
- # @example With id
+ # User.exists?(['713', '714', '715'])
#
- # Post.exist?(713)
- # Post.exist?([713, 210])
+ # Or in case when a range key is declared:
#
- # @example With attributes conditions
+ # User.exists?(
+ # [
+ # ['713', 'range-key-value-1'],
+ # ['714', 'range-key-value-2'],
+ # ['715', 'range-key-value-3']
+ # ]
+ # )
#
- # Post.exist?(version: 1, 'created_at.gt': Time.now - 1.day)
+ # It's also possible to specify models not with primary key but with
+ # conditions on the attributes (in the +where+ method style):
#
+ # User.exists?(age: 20, 'created_at.gt': Time.now - 1.day)
+ #
+ # @param id_or_conditions [String|Array[String]|Array[Array]|Hash] the primary id of the model, a list of primary ids or a hash with the options to filter from.
+ # @return [true|false]
# @since 0.2.0
def exists?(id_or_conditions = {})
case id_or_conditions
when Hash then where(id_or_conditions).count >= 1
else
@@ -119,27 +164,37 @@
false
end
end
end
+ # @private
def deep_subclasses
subclasses + subclasses.map(&:deep_subclasses).flatten
end
+ # @private
def choose_right_class(attrs)
attrs[inheritance_field] ? attrs[inheritance_field].constantize : self
end
end
# Initialize a new object.
#
- # @param [Hash] attrs Attributes with which to create the object.
+ # User.new(name: 'A')
#
+ # Initialize an object and pass it into a block to set other attributes.
+ #
+ # User.new(name: 'A') do |u|
+ # u.age = 21
+ # end
+ #
+ # @param attrs [Hash] Attributes with which to create the document
+ # @param block [Proc] Block to process a document after initialization
# @return [Dynamoid::Document] the new document
#
# @since 0.2.0
- def initialize(attrs = {})
+ def initialize(attrs = {}, &block)
run_callbacks :initialize do
@new_record = true
@attributes ||= {}
@associations ||= {}
@attributes_before_type_cast ||= {}
@@ -153,15 +208,23 @@
end
attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
load(attrs_with_defaults.merge(attrs_virtual))
+
+ if block
+ block.call(self)
+ end
end
end
- # An object is equal to another object if their ids are equal.
+ # Check equality of two models.
#
+ # A model is equal to another model only if their primary keys (hash key
+ # and optionaly range key) are equal.
+ #
+ # @return [true|false]
# @since 0.2.0
def ==(other)
if self.class.identity_map_on?
super
else
@@ -169,40 +232,58 @@
other.is_a?(Dynamoid::Document) && hash_key == other.hash_key && range_value == other.range_value
end
end
+ # Check equality of two models.
+ #
+ # Works exactly like +==+ does.
+ #
+ # @return [true|false]
def eql?(other)
self == other
end
+ # Generate an Integer hash value for this model.
+ #
+ # Hash value is based on primary key. So models can be used safely as a
+ # +Hash+ keys.
+ #
+ # @return [Integer]
def hash
hash_key.hash ^ range_value.hash
end
- # Return an object's hash key, regardless of what it might be called to the object.
+ # Return a model's hash key value.
#
# @since 0.4.0
def hash_key
- send(self.class.hash_key)
+ self[self.class.hash_key.to_sym]
end
- # Assign an object's hash key, regardless of what it might be called to the object.
+ # Assign a model's hash key value, regardless of what it might be called to
+ # the object.
#
# @since 0.4.0
def hash_key=(value)
- send("#{self.class.hash_key}=", value)
+ self[self.class.hash_key.to_sym] = value
end
+ # Return a model's range key value.
+ #
+ # Returns +nil+ if a range key isn't declared for a model.
def range_value
- if range_key = self.class.range_key
- send(range_key)
+ if self.class.range_key
+ self[self.class.range_key.to_sym]
end
end
+ # Assign a model's range key value.
def range_value=(value)
- send("#{self.class.range_key}=", value)
+ if self.class.range_key
+ self[self.class.range_key.to_sym] = value
+ end
end
private
def dumped_range_value
@@ -210,10 +291,10 @@
end
# Evaluates the default value given, this is used by undump
# when determining the value of the default given for a field options.
#
- # @param [Object] :value the attribute's default value
+ # @param val [Object] the attribute's default value
def evaluate_default_value(val)
if val.respond_to?(:call)
val.call
elsif val.duplicable?
val.dup