lib/dynamoid/associations/many_association.rb
# frozen_string_literal: true
module Dynamoid
module Associations
module ManyAssociation
include Association
attr_accessor :query
def initialize(*args)
@query = {}
super
end
include Enumerable
# @private
# Delegate methods to the records the association represents.
delegate :first, :last, :empty?, :size, :class, to: :records
# The records associated to the source.
#
# @return the association records; depending on which association this is, either a single instance or an array
#
# @private
# @since 0.2.0
def find_target
return [] if source_ids.empty?
Array(target_class.find(source_ids.to_a, raise_error: false))
end
# @private
def records
if query.empty?
target
else
results_with_query(target)
end
end
# Alias convenience methods for the associations.
alias all records
alias count size
alias nil? empty?
# Delegate include? to the records.
def include?(object)
records.include?(object)
end
# Delete an object or array of objects from the association.
#
# tag.posts.delete(post)
# tag.posts.delete([post1, post2, post3])
#
# This removes their records from the association field on the source,
# and attempts to remove the source from the target association if it is
# detected to exist.
#
# It saves both models immediately - the source model and the target one
# so any not saved changes will be saved as well.
#
# @param object [Dynamoid::Document|Array] model (or array of models) to remove from the association
# @return [Dynamoid::Document|Array] the deleted model
# @since 0.2.0
def delete(object)
disassociate(Array(object).collect(&:hash_key))
if target_association
Array(object).each { |obj| obj.send(target_association).disassociate(source.hash_key) }
end
object
end
# Add an object or array of objects to an association.
#
# tag.posts << post
# tag.posts << [post1, post2, post3]
#
# This preserves the current records in the association (if any) and adds
# the object to the target association if it is detected to exist.
#
# It saves both models immediately - the source model and the target one
# so any not saved changes will be saved as well.
#
# @param object [Dynamoid::Document|Array] model (or array of models) to add to the association
# @return [Dynamoid::Document] the added model
# @since 0.2.0
def <<(object)
associate(Array(object).collect(&:hash_key))
if target_association
Array(object).each { |obj| obj.send(target_association).associate(source.hash_key) }
end
object
end
# Replace an association with object or array of objects. This removes all of the existing associated records and replaces them with
# the passed object(s), and associates the target association if it is detected to exist.
#
# @param [Dynamoid::Document] object the object (or array of objects) to add to the association
#
# @return [Dynamoid::Document|Array] the added object
#
# @private
# @since 0.2.0
def setter(object)
target.each { |o| delete(o) }
self << object
object
end
# Create a new instance of the target class, persist it and add directly
# to the association.
#
# tag.posts.create!(title: 'foo')
#
# Several models can be created at once when an array of attributes
# specified:
#
# tag.posts.create!([{ title: 'foo' }, {title: 'bar'} ])
#
# If the creation fails an exception will be raised.
#
# @param attributes [Hash] attribute values for the new object
# @return [Dynamoid::Document|Array] the newly-created object
# @since 0.2.0
def create!(attributes = {})
self << target_class.create!(attributes)
end
# Create a new instance of the target class, persist it and add directly
# to the association.
#
# tag.posts.create(title: 'foo')
#
# Several models can be created at once when an array of attributes
# specified:
#
# tag.posts.create([{ title: 'foo' }, {title: 'bar'} ])
#
# @param attributes [Hash] attribute values for the new object
# @return [Dynamoid::Document|Array] the newly-created object
# @since 0.2.0
def create(attributes = {})
self << target_class.create(attributes)
end
# Create a new instance of the target class and add it directly to the association. If the create fails an exception will be raised.
#
# @return [Dynamoid::Document] the newly-created object
#
# @private
# @since 0.2.0
def each(&block)
records.each(&block)
end
# Destroys all members of the association and removes them from the
# association.
#
# tag.posts.destroy_all
#
# @since 0.2.0
def destroy_all
objs = target
source.update_attribute(source_attribute, nil)
objs.each(&:destroy)
end
# Deletes all members of the association and removes them from the
# association.
#
# tag.posts.delete_all
#
# @since 0.2.0
def delete_all
objs = target
source.update_attribute(source_attribute, nil)
objs.each(&:delete)
end
# Naive association filtering.
#
# tag.posts.where(title: 'foo')
#
# It loads lazily all the associated models and checks provided
# conditions. That's why only equality conditions can be specified.
#
# @param args [Hash] A hash of attributes; each must match every returned object's attribute exactly.
# @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
# @since 0.2.0
def where(args)
filtered = clone
filtered.query = query.clone
args.each { |k, v| filtered.query[k] = v }
filtered
end
# Is this array equal to the association's records?
#
# @return [Boolean] true/false
#
# @since 0.2.0
def ==(other)
records == Array(other)
end
# Delegate methods we don't find directly to the records array.
#
# @private
# @since 0.2.0
def method_missing(method, *args)
if records.respond_to?(method)
records.send(method, *args)
else
super
end
end
# @private
def associate(hash_key)
source.update_attribute(source_attribute, source_ids.merge(Array(hash_key)))
end
# @private
def disassociate(hash_key)
source.update_attribute(source_attribute, source_ids - Array(hash_key))
end
private
# If a query exists, filter all existing results based on that query.
#
# @param [Array] results the raw results for the association
#
# @return [Array] the filtered results for the query
#
# @since 0.2.0
def results_with_query(results)
results.find_all do |result|
query.all? do |attribute, value|
result.send(attribute) == value
end
end
end
end
end
end