module ReactiveRecord # methods to update aggregrations and relations, called from reactive_set! class Base def update_aggregate(attribute, value) # if attribute is an aggregate then # match and update all fields in the aggregate from value and return true # otherwise return false aggregation = @model.reflect_on_aggregation(attribute) return false unless aggregation && (aggregation.klass < ActiveRecord::Base) if value value_attributes = value.backing_record.attributes update_mapped_attributes(aggregation) { |attr| value_attributes[attr] } else update_mapped_attributes(aggregation) { nil } end true end def update_mapped_attributes(aggregation) # insure the aggregate attr is initialized, clear the virt flag, the caller # will yield each of the matching attribute values attr = aggregation.attribute attributes[attr] ||= aggregation.klass.new if new? aggregate_record = attributes[attr] raise 'uninitialized aggregate attribute - should never happen' unless aggregate_record aggregate_backing_record = aggregate_record.backing_record aggregate_backing_record.virgin = false aggregation.mapped_attributes.each do |mapped_attribute| aggregate_backing_record.update_attribute(mapped_attribute, yield(mapped_attribute)) end end def update_relationships(attr, value) # update the inverse relationship, and any through relationships # return either the value, or in the case of updating a collection # return the new collection after value is overwritten into it. association = @model.reflect_on_association(attr) return value unless association if association.collection? overwrite_has_many_collection(association, value) else update_belongs_to_association(association, value) value end end def overwrite_has_many_collection(association, value) # create a new collection to hold value, shove it in, and return the new collection # the replace method will take care of updating the inverse belongs_to links as # the collection is overwritten Collection.new(association.klass, @ar_instance, association).tap do |collection| collection.replace(value || []) end end def update_belongs_to_association(association, value) # either update update the inverse has_many collection or individual belongs_to # inverse values if association.inverse.collection? update_has_many_through_associations(association, value) update_inverse_collections(association, value) else update_inverse_attribute(association, value) end end def update_inverse_attribute(association, value) # when updating the inverse attribute of a belongs_to that is itself a belongs_to # (i.e. 1-1 relationship) we clear the existing inverse value and then # write the current record to the new value current_value = attributes[association.attribute] inverse_attr = association.inverse.attribute current_value.attributes[inverse_attr] = nil unless current_value.nil? return if value.nil? value.attributes[inverse_attr] = @ar_instance return if data_loading? React::State.set_state(value.backing_record, inverse_attr, @ar_instance) end def update_inverse_collections(association, value) # when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection) # we need to first remove the current associated value (if non-nil), then add the new # value to the collection. If the inverse collection is not yet initialized we do it here. current_value = attributes[association.attribute] inverse_attr = association.inverse.attribute if value.nil? current_value.attributes[inverse_attr].delete(@ar_instance) unless current_value.nil? else value.backing_record.push_onto_collection(association.inverse, @ar_instance) end end def push_onto_collection(association, ar_instance) attributes[association.attribute] ||= Collection.new(@model, @ar_instance, association) attributes[association.attribute] << ar_instance end def update_has_many_through_associations(association, value) association.through_associations.each { |ta| update_through_association(ta, value) } association.source_associations.each { |sa| update_source_association(sa, value) } end def update_through_association(ta, new_belongs_to_value) # appointment.doctor = doctor_new_value (i.e. through association is changing) # means appointment.doctor_new_value.patients << appointment.patient # and we have to appointment.doctor_current_value.patients.delete(appointment.patient) source_value = attributes[ta.source] current_belongs_to_value = attributes[ta.inverse.attribute] return unless source_value unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil? current_belongs_to_value.attributes[ta.attribute].delete(source_value) end return unless new_belongs_to_value new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(ta.klass, new_belongs_to_value, ta) new_belongs_to_value.attributes[ta.attribute] << source_value end def update_source_association(sa, new_source_value) # appointment.patient = patient_value (i.e. source is changing) # means appointment.doctor.patients.delete(appointment.patient) # means appointment.doctor.patients << patient_value belongs_to_value = attributes[sa.inverse.attribute] current_source_value = attributes[sa.source] return unless belongs_to_value unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil? belongs_to_value.attributes[sa.attribute].delete(current_source_value) end return unless new_source_value belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass, belongs_to_value, sa) belongs_to_value.attributes[sa.attribute] << new_source_value end end end # def reactive_set!(attribute, value) # @virgin = false unless data_loading? # unless @destroyed or (!(attributes[attribute].is_a? DummyValue) and attributes.has_key?(attribute) and attributes[attribute] == value) # if association = @model.reflect_on_association(attribute) # if association.collection? # collection = Collection.new(association.klass, @ar_instance, association) # collection.replace(value || []) # value = collection # else # inverse_of = association.inverse_of # inverse_association = association.klass.reflect_on_association(inverse_of) # if inverse_association.collection? # if value.nil? # attributes[attribute].attributes[inverse_of].delete(@ar_instance) unless attributes[attribute].nil? # elsif value.attributes[inverse_of] # value.attributes[inverse_of] << @ar_instance # else # value.attributes[inverse_of] = Collection.new(@model, value, inverse_association) # # value.attributes[inverse_of].replace [@ar_instance] # # why was the above not just the below???? fixed 10/28/2016 # value.attributes[inverse_of] << @ar_instance # end # elsif !value.nil? # attributes[attribute].attributes[inverse_of] = nil unless attributes[attribute].nil? # value.attributes[inverse_of] = @ar_instance # React::State.set_state(value.backing_record, inverse_of, @ar_instance) unless data_loading? # elsif attributes[attribute] # attributes[attribute].attributes[inverse_of] = nil # end # end # elsif aggregation = @model.reflect_on_aggregation(attribute) and (aggregation.klass < ActiveRecord::Base) # # if new? # attributes[attribute] ||= aggregation.klass.new # elsif !attributes[attribute] # raise "uninitialized aggregate attribute - should never happen" # end # # aggregate_record = attributes[attribute].backing_record # aggregate_record.virgin = false # # if value # value_attributes = value.backing_record.attributes # aggregation.mapped_attributes.each { |mapped_attribute| aggregate_record.update_attribute(mapped_attribute, value_attributes[mapped_attribute])} # else # aggregation.mapped_attributes.each { |mapped_attribute| aggregate_record.update_attribute(mapped_attribute, nil) } # end # # return attributes[attribute] # # end # update_attribute(attribute, value) # end # value # end