# frozen_string_literal: true # Tableless model to handle updating multiple models at once from a single form module Sets class ModelSet include ActiveModel::Conversion extend ActiveModel::Naming attr_accessor :collection def initialize(klass, collection, attributes = {}, reject_if = nil, delete_if = nil) @klass, @collection, @reject_if, @delete_if = klass, collection, reject_if, delete_if # Set here first, to ensure that we apply collection_attributes to the right collection @collection = attributes[:collection] if attributes[:collection] @collection = @collection.to_a attributes.each do |name, value| public_send("#{name}=", value) end end def collection_attributes=(collection_attributes) collection_attributes.each_value do |attributes| # attributes == {:id => 123, :next_collection_at => '...'} found_element = @collection.detect do |element| element.id.to_s == attributes[:id].to_s && !element.id.nil? end if found_element.nil? @collection << @klass.new(attributes) unless @reject_if&.call(attributes) else process(found_element, attributes) end end end # The request params are assigned to the fetched record here, becoming dirty and ready to # be persisted in DB as an UPDATE. # # This enables having different process strategies depending on the concrete model set class, if # we choose to. This will be the default implementation. def process(found_element, attributes) found_element.assign_attributes(attributes.except(:id)) end def errors errors = ActiveModel::Errors.new self full_messages = @collection .map { |model| model.errors.full_messages } .flatten full_messages.each { |message| errors.add(:base, message) } errors end def invalid @collection.select { |model| model.errors.any? } end def save collection_to_delete.each(&:destroy) collection_to_keep.all?(&:save) end def collection_to_delete # Remove all elements to be deleted from collection and return them # Allows us to render @model_set.collection without deleted elements deleted = [] @collection = collection.to_a collection.delete_if { |e| deleted << e if @delete_if&.call(e.attributes) } deleted end def collection_to_keep collection.reject { |e| @delete_if&.call(e.attributes) } end def persisted? false end def find_model(collection, model_id) collection.find do |model| model.id.to_s == model_id.to_s && model.persisted? end end end end