lib/promiscuous/subscriber/model/mongoid.rb
module Promiscuous::Subscriber::Model::Mongoid
extend ActiveSupport::Concern
include Promiscuous::Subscriber::Model::Base
def __promiscuous_update(payload, options={})
super
# The return value tells if the parent should assign the attribute
!self.embedded? || options[:old_value] != self
end
included do
field :_v
end
module ClassMethods
def subscribe(*args, &block)
super
return unless block
begin
@in_subscribe_block = true
block.call
ensure
@in_subscribe_block = false
end
end
def self.subscribe_on(method, options={})
define_method(method) do |name, *args, &block|
super(name, *args, &block)
if @in_subscribe_block
name = args.last[:as] if args.last.is_a?(Hash) && args.last[:as]
subscribe(name)
end
end
end
subscribe_on :field
subscribe_on :embeds_one
subscribe_on :embeds_many
def __promiscuous_missing_record_exception
Mongoid::Errors::DocumentNotFound
end
def __promiscuous_duplicate_key_exception?(e)
e.message =~ /E11000/
end
def __promiscuous_fetch_existing(id)
key = subscribe_foreign_key
instance = if respond_to?("find_by")
promiscuous_root_class.find_by(key => id)
else
instance = promiscuous_root_class.where(key => id).first
raise __promiscuous_missing_record_exception.new(promiscuous_root_class, id) if instance.nil?
instance
end
# This allows for subscribing initially only to a root class and replicating data
# and then add subscribing to subclasses and having the type of existing
# data automatically update (avoiding the confusing workaround of having
# to delete existing data).
example_instance = self.new
if example_instance.respond_to?(:_type)
expected_type = example_instance._type
if instance._type != expected_type
collection.find(instance.atomic_selector).update('$set' => { :_type => expected_type })
instance = __promiscuous_fetch_existing(id)
end
end
instance
end
end
class EmbeddedDocs
include Promiscuous::Subscriber::Model::Base
subscribe :as => 'Promiscuous::EmbeddedDocs'
def __promiscuous_update(payload, options={})
old_embeddeds = options[:old_value]
new_embeddeds = payload.attributes
# XXX Reordering is not supported
# find all updatable docs
new_embeddeds.each do |new_e|
old_e = old_embeddeds.select { |e| e.id.to_s == new_e['id'] }.first
if old_e
new_e['existed'] = true
old_e.instance_variable_set(:@keep, true)
payload = Promiscuous::Subscriber::Operation.new(new_e)
old_e.__promiscuous_update(payload, :old_value => old_e)
end
end
# delete all the old ones
old_embeddeds.each do |old_e|
old_e.destroy unless old_e.instance_variable_get(:@keep)
end
# create all the new ones
new_embeddeds.reject { |new_e| new_e['existed'] }.each do |new_e|
payload = Promiscuous::Subscriber::Operation.new(new_e)
new_e_instance = payload.model.__promiscuous_fetch_new(payload.id)
new_e_instance.__promiscuous_update(payload)
options[:parent].__send__(old_embeddeds.metadata[:name]) << new_e_instance
end
false
end
def self.__promiscuous_fetch_new(id)
new
end
end
end