module Scrivito class AttributeSerializer < Struct.new(:obj_class, :obj_id) def serialize(attributes, attribute_definitions) return attributes if attributes.blank? serialized_attributes = attributes.map do |attribute_name, attribute_value| serialize_attribute(attribute_name, attribute_value, attribute_definitions) end Hash[serialized_attributes] end private def serialize_attribute(attribute_name, attribute_value, attribute_definitions) attribute_name = attribute_name.to_s if attribute_name.starts_with?('_') serialize_system_attribute(attribute_name, attribute_value) else attribute_definition = attribute_definitions[attribute_name] serialize_custom_attribute(attribute_name, attribute_value, attribute_definition) end end def serialize_system_attribute(attribute_name, attribute_value) if %w[ _id _obj_class _path _permalink ].include?(attribute_name) [attribute_name, attribute_value.to_s] else raise_unknown_attribute(attribute_name) end end def serialize_custom_attribute(attribute_name, attribute_value, attribute_definition) raise_unknown_attribute(attribute_name) unless attribute_definition attribute_type = attribute_definition.type [attribute_name, [ serialize_attribute_type(attribute_type), serialize_attribute_value(attribute_type, attribute_value, attribute_definition) ]] end def serialize_attribute_type(attribute_type) case attribute_type when 'enum' then 'string' when 'float', 'integer' then 'number' when 'multienum' then 'stringlist' else attribute_type end end def serialize_attribute_value(attribute_type, attribute_value, attribute_definition) return unless attribute_value case attribute_type when 'binary' then serialize_binary_value(attribute_value, attribute_definition) when 'date' then serialize_date_value(attribute_value, attribute_definition) when 'enum' then serialize_enum_value(attribute_value, attribute_definition) when 'float' then serialize_float_value(attribute_value, attribute_definition) when 'html' then serialize_html_value(attribute_value) when 'integer' then serialize_integer_value(attribute_value, attribute_definition) when 'link' then serialize_link_value(attribute_value, attribute_definition) when 'linklist' then serialize_linklist_value(attribute_value, attribute_definition) when 'multienum' then serialize_multienum_value(attribute_value, attribute_definition) when 'reference' then serialize_reference_value(attribute_value) when 'referencelist' then serialize_referencelist_value(attribute_value, attribute_definition) when 'string' then serialize_string_value(attribute_value) when 'stringlist' then serialize_stringlist_value(attribute_value, attribute_definition) when 'widgetlist' then serialize_widgetlist_value(attribute_value, attribute_definition) end end def serialize_binary_value(attribute_value, attribute_definition) attribute_value = Binary.upload(attribute_value) if attribute_value.is_a?(File) case attribute_value when FutureBinary then CmsRestApi.upload_future_binary(attribute_value, obj_id) when UploadedBinary then attribute_value.params else raise_validation_error(attribute_definition.name, 'an instance of File, Scrivito::UploadedBinary or nil', attribute_value) end end def serialize_date_value(attribute_value, attribute_definition) attribute_value = DateConversion.serialize_for_backend(attribute_value) unless attribute_value raise_validation_error(attribute_definition.name, 'an instance of Date or Time', attribute_value) end attribute_value end def serialize_enum_value(attribute_value, attribute_definition) attribute_value = attribute_value.to_s attribute_values = attribute_definition.values if attribute_values.include?(attribute_value) attribute_value else raise_validation_error(attribute_definition.name, format_array(attribute_values), attribute_value) end end def serialize_float_value(attribute_value, attribute_definition) attribute_name = attribute_definition.name case attribute_value when Integer, Float FloatConversion.serialize_for_backend(attribute_value, attribute_name) else raise_validation_error(attribute_name, 'an instance of Float or Integer', attribute_value) end end def serialize_html_value(attribute_value) attribute_value.to_s end def serialize_integer_value(attribute_value, attribute_definition) attribute_name = attribute_definition.name unless Integer === attribute_value raise_validation_error(attribute_name, 'an instance of Integer', attribute_value) end IntegerConversion.serialize_for_backend(attribute_value, attribute_name) end def serialize_link_value(attribute_value, attribute_definition) if attribute_value.is_a?(Link) attribute_value.to_cms_api_linklist_params else raise_validation_error(attribute_definition.name, 'an instance of Scrivito::Link', attribute_value) end end def serialize_linklist_value(attribute_value, attribute_definition) if attribute_value.is_a?(Enumerable) && attribute_value.all? { |item| item.is_a?(Link) } attribute_value.map(&:to_cms_api_linklist_params) else raise_validation_error(attribute_definition.name, 'Enumerable containing instances of Scrivito::Link', attribute_value) end end def serialize_multienum_value(attribute_value, attribute_definition) if attribute_value.is_a?(Enumerable) attribute_value = attribute_value.map(&:to_s) attribute_values = attribute_definition.values forbidden_values = attribute_value - attribute_values if forbidden_values.any? raise_validation_error(attribute_definition.name, format_array(attribute_values), attribute_value) end attribute_value else raise_validation_error(attribute_definition.name, 'Enumerable containing objects', attribute_value) end end def serialize_reference_value(attribute_value) attribute_value.is_a?(BasicObj) ? attribute_value.id : attribute_value.to_s end def serialize_referencelist_value(attribute_value, attribute_definition) if attribute_value.is_a?(Enumerable) attribute_value.map(&method(:serialize_reference_value)) else raise_validation_error(attribute_definition.name, 'Enumerable', attribute_value) end end def serialize_string_value(attribute_value) attribute_value.to_s end def serialize_stringlist_value(attribute_value, attribute_definition) if attribute_value.is_a?(Enumerable) attribute_value.map(&:to_s) else raise_validation_error(attribute_definition.name, 'Enumerable containing instances of String', attribute_value) end end def serialize_widgetlist_value(attribute_value, attribute_definition) attribute_name = attribute_definition.name if !attribute_value.is_a?(Enumerable) || attribute_value.any? { |i| !i.is_a?(BasicWidget) } raise_validation_error(attribute_name, 'Enumerable containing instances of Scrivito::BasicWidget', attribute_value) end attribute_value.each do |widget| if widget.persisted? && widget.obj.id != obj_id error_message = %{ Tried to put #{widget.inspect} from a different Obj into attribute #{attribute_name}. Moving widgets between Objs is not possible, but you can copy Widgets using Obj#copy. } raise ClientError.new(error_message) end end attribute_value.map(&:id) end def raise_validation_error(attribute_name, expected_value, actual_value) error_message = "Unexpected value #{actual_value.inspect} for attribute #{attribute_name}."\ " Expected: #{expected_value}." raise ClientError.new(error_message) end def raise_unknown_attribute(attribute_name) raise ScrivitoError, "Unknown attribute #{attribute_name} for #{obj_class}" end def format_array(values) values.map(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ' or ') end end end