mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
90 lines
2.7 KiB
Ruby
90 lines
2.7 KiB
Ruby
# 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
|