diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 3ac89c9b..c8be3a51 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -4,19 +4,25 @@ require 'filemagic' # Define un campo de archivo class MetadataFile < MetadataTemplate + include Metadata::NonIndexableConcern + include Metadata::AlwaysPublicConcern + # Una ruta vacía a la imagen con una descripción vacía def default_value - super || { 'path' => nil, 'description' => nil } + super || { 'path' => nil, 'description' => '' } end + # La descripción es opcional + # + # @return [Boolean] def empty? - value == default_value + value.nil? || value['path'].blank? end # No hay valores sugeridos para archivos subidos. - # - # XXX: Esto ayuda a deserializar en {Site#everything_of} - def values; end + def values + raise NotImplementedError, "#{self.class} no tiene valores sugeridos" + end def validate super @@ -29,25 +35,15 @@ class MetadataFile < MetadataTemplate errors.empty? end - # Determina si necesitamos la imagen pero no la tenemos - def path_missing? - required && !path? - end - - # Determina si el archivo ya fue subido - def uploaded? - value['path'].is_a?(String) - end - # Asociar la imagen subida al sitio y obtener la ruta # @return [Boolean] def save - if value['path'].blank? - self[:value] = default_value - else - value['description'] = sanitize value['description'] - value['path'] = relative_destination_path_with_filename.to_s if static_file - end + return true unless changed? + + self[:value] = default_value if empty? + self[:value] = sanitize(value) + + value['path'] = relative_destination_path_with_filename.to_s if static_file true end @@ -75,32 +71,20 @@ class MetadataFile < MetadataTemplate end end - # Obtiene la ruta absoluta al archivo - # - # @return [Pathname] - def pathname - raise NoMethodError unless uploaded? - - @pathname ||= Pathname.new(File.join(site.path, value['path'])) - end - - # Obtiene la key del attachment a partir de la ruta - # - # @return [String] - def key_from_path - @key_from_path ||= pathname.dirname.basename.to_s - end - - def path? - value['path'].present? - end - - def description? - value['description'].present? - end - private + # Valida que estemos pasando el formato correcto + # + # @param :value [Any] + # @return [Hash,nil] + def sanitize(value) + return unless value.is_a? Hash + + value.dup.tap do |v| + v['description'] = super(v['description']) + end + end + # Obtener la ruta al archivo relativa al sitio # # @return [Pathname] @@ -135,6 +119,10 @@ class MetadataFile < MetadataTemplate value['path'] end + # La ruta absoluta al archivo + # + # @todo Eliminar retrocompatibilidad + # @return [String] def static_file_path case static_file.blob.service.name when :local @@ -171,4 +159,39 @@ class MetadataFile < MetadataTemplate ExceptionNotifier.notify_exception(e, data: { site: site.name, path: value['path'] }) nil end + + # Determina si necesitamos la imagen pero no la tenemos + def path_missing? + required && !path? + end + + # Determina si el archivo ya fue subido + def uploaded? + value['path'].is_a?(String) + end + + # Obtiene la ruta absoluta al archivo + # + # @return [Pathname] + def pathname + raise NoMethodError unless uploaded? + + @pathname ||= Pathname.new(File.join(site.path, value['path'])) + end + + # Obtiene la key del attachment a partir de la ruta + # + # @return [String] + def key_from_path + @key_from_path ||= pathname.dirname.basename.to_s + end + + # @todo Este método no se puede correr sobre archivos recién subidos + def path? + value['path'].present? + end + + def description? + value['description'].present? + end end diff --git a/test/models/metadata_file_test.rb b/test/models/metadata_file_test.rb new file mode 100644 index 00000000..8d8b6806 --- /dev/null +++ b/test/models/metadata_file_test.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'test_helper' + +class MetadataFileTest < ActiveSupport::TestCase + setup do + @site = build(:site) + @document = build(:document, site: @site.jekyll) + @name = SecureRandom.hex + @layout = build(:layout, site: @site) + @metadata = MetadataFile.new(site: @site, document: @document, name: @name, type: 'file', layout: @layout) + end + + def random_value + { + 'path' => "public/#{SecureRandom.base36(28)}/#{SecureRandom.hex}.pdf", + 'description' => SecureRandom.hex + } + end + + test 'se guarda en el encabezado' do + assert @metadata.front_matter? + end + + test 'por defecto es una ruta vacía' do + assert_equal ({ 'path' => nil, 'description' => '' }), @metadata.default_value + end + + test 'puede traer el valor desde el documento' do + @document.data[@name] = random_value + + assert_equal @document[@name], @metadata.document_value + assert @metadata.save + assert_equal @document[@name], @metadata.value + end + + test 'al cambiar el valor podemos obtenerlo' do + @metadata.value = value_was = random_value + @metadata.value = random_value + + assert_equal value_was, @metadata.value_was + assert_not_equal value_was, @metadata.value + end + + test 'pueden tener un valor por defecto desde el layout' do + default = random_value + + @layout = build(:layout, site: @site, metadata: { @name => { 'default' => { I18n.locale.to_s => default } } }) + @metadata = MetadataFile.new(site: @site, document: @document, name: @name, type: 'file', layout: @layout) + + assert_equal default, @metadata.default_value + end + + test 'no puede estar vacía si es obligatoria' do + @layout = build(:layout, site: @site, metadata: { @name => { 'default' => { 'required' => true } } }) + @metadata = MetadataFile.new(site: @site, document: @document, name: @name, type: 'file', layout: @layout, + required: true) + + assert @metadata.required + assert @metadata.empty? + assert_not @metadata.valid? + end + + test 'no se pueden indexar' do + assert_not @metadata.indexable? + end + + test 'son públicos por defecto' do + assert_not @metadata.private? + end + + test 'no se pueden hacer privados' do + @layout = build(:layout, site: @site, metadata: { @name => { 'private' => true } }) + @metadata = MetadataFile.new(site: @site, document: @document, name: @name, type: 'file', layout: @layout) + + assert_not @metadata.private? + end + + test 'se les puede asignar un valor' do + @metadata.value = value = random_value + + assert_equal value, @metadata.value + end + + test 'se lo considera vacío si no se asigna un archivo' do + assert @metadata.empty? + end + + test 'se puede subir un archivo' do + value = random_value + value['path'] = + ActionDispatch::Http::UploadedFile.new(tempfile: Tempfile.new, filename: value['path'].split('/').last) + + # XXX: por alguna razón no se guarda esto en cascada + @site.design.save + @site.design_id = @site.design.id + @site.save + + @metadata.value = value + + assert @metadata.valid? + assert @metadata.save + assert_instance_of String, @metadata.value['path'] + assert_instance_of ActiveStorage::Attachment, @metadata.static_file + assert_not @metadata.empty? + ensure + @site.destroy + end + + test 'se puede volver a encontrar un archivo subido' do + value = random_value + value['path'] = + ActionDispatch::Http::UploadedFile.new(tempfile: Tempfile.new, filename: value['path'].split('/').last) + + @site.design.save + @site.design_id = @site.design.id + @site.save + + @metadata.value = value + @metadata.save + + @metadata.instance_variable_set :@static_file, nil + + assert_instance_of String, @metadata.value['path'] + assert_instance_of ActiveStorage::Attachment, @metadata.static_file + assert_not @metadata.empty? + ensure + @site.destroy + end + + test 'si el archivo esta en el repositorio lo podemos asociar al sitio' do + value = random_value + + @site.design.save + @site.design_id = @site.design.id + @site.save + + path = File.join(@site.path, value['path']) + FileUtils.mkdir_p File.dirname(path) + + File.write(path, 'test') + + @metadata.value = value + @metadata.save + + assert_instance_of ActiveStorage::Attachment, @metadata.static_file + ensure + @site.destroy + end +end