module MotionPrime module ModelAssociationMixin extend ::MotionSupport::Concern def _bags @_bags ||= {} end # Saves model and all associations to store. # # @return [Prime::Model] model def save _bags.values.each do |bag| bag.store = self.store bag.save end super rescue StoreError => e if Prime.env.development? raise StoreError, e.description end end module ClassMethods # Defines bag associated with model, creates accessor for bag # # @param [String] name - the name of bag # @return [Nil] def bag(name) define_method(name) do |*args, &block| return _bags[name] if _bags[name] bag_key = self.info[name] if bag_key.present? bag = self.class.store.bagsWithKeysInArray([bag_key]).first end unless bag bag = Bag.bag self.info[name] = bag.key end _bags[name] = bag end define_method((name + "=").to_sym) do |*args, &block| bag = self.send(name) case args[0] when Bag bag.clear bag += args[0].saved.values when Array bag.clear bag += args[0] else raise StoreError, "Unexpected type assigned to bags, must be an Array or MotionPrime::Bag, now: #{args[0].class}" end bag end end # Defines has one association for model, creates accessor for association # # @param [String] name - the name of association # @return [Nil] def has_one(association_name, options = {}) bag_name = "#{association_name.pluralize}_bag" self.bag bag_name.to_sym self._associations ||= {} self._associations[association_name] = options.merge(type: :one) define_method("#{association_name}=") do |value| self.send(bag_name).clear self.send(:"#{bag_name}") << value value end define_method("#{association_name}_attributes=") do |value| self.send(bag_name).clear association = association_name.classify.constantize.new association.fetch_with_attributes(value) association.save self.send(:"#{bag_name}") << association self.send(:"#{bag_name}").save association end define_method("#{association_name}") do self.send(:"#{bag_name}").to_a.first end end # Defines has many association for model, creates accessor for association # # @param [String] name - the name of association # @return [Nil] def has_many(association_name, options = {}) bag_name = "#{association_name}_bag" self.bag bag_name.to_sym self._associations ||= {} self._associations[association_name] = options.merge(type: :many) define_method("#{association_name}_attributes=") do |value| self.send(bag_name).clear pending_save_counter = 0 collection = value.inject({}) do |result, attrs| model = association_name.classify.constantize.new model.fetch_with_attributes(attrs) unique_key = model.id || "pending_#{pending_save_counter+=1}" result.merge(unique_key => model) end association_data = collection.values self.send(:"#{bag_name}=", association_data) self.send(:"#{bag_name}").save association_data end define_method("#{association_name}=") do |value| self.send(bag_name).clear self.send(:"#{bag_name}=", value) end define_method("#{association_name}") do |*args| bag = self.send(:"#{bag_name}") collection_options = { association_name: association_name, inverse_relation: { type: :has_one, name: self.class_name_without_kvo.demodulize.underscore, instance: self } } AssociationCollection.new(bag, collection_options, *args) end end def belongs_to(association_name, options = {}) self._associations ||= {} self._associations[association_name] = { type: :belongs_to_one, class_name: association_name.classify }.merge(options) self.send(:attr_accessor, association_name) end end end end