diff --git a/app/models/metadata_belongs_to.rb b/app/models/metadata_belongs_to.rb index 0626ba0..ee182a5 100644 --- a/app/models/metadata_belongs_to.rb +++ b/app/models/metadata_belongs_to.rb @@ -3,13 +3,6 @@ # Almacena el UUID de otro Post y actualiza el valor en el Post # relacionado. class MetadataBelongsTo < MetadataRelatedPosts - def value_was=(new_value) - @belongs_to = nil - @belonged_to = nil - - super(new_value) - end - # TODO: Convertir algunos tipos de valores en módulos para poder # implementar varios tipos de campo sin repetir código # @@ -39,10 +32,14 @@ class MetadataBelongsTo < MetadataRelatedPosts # Si estamos cambiando la relación, tenemos que eliminar la relación # anterior - belonged_to[inverse].value.delete post.uuid.value if changed? && belonged_to.present? + if belonged_to.present? + belonged_to[inverse].value = belonged_to[inverse].value.reject do |rej| + rej == post.uuid.value + end + end # No duplicar las relaciones - belongs_to[inverse].value << post.uuid.value unless belongs_to.blank? || included? + belongs_to[inverse].value = (belongs_to[inverse].value.dup << post.uuid.value) unless belongs_to.blank? || included? true end @@ -63,20 +60,13 @@ class MetadataBelongsTo < MetadataRelatedPosts end # El Post relacionado con este artículo - # - # XXX: Memoizamos usando el valor para tener el valor siempre - # actualizado. def belongs_to - return if value.blank? - - @belongs_to ||= posts.find(value, uuid: true) + posts.find(value, uuid: true) if value.present? end # El artículo relacionado anterior def belonged_to - return if value_was.blank? - - @belonged_to ||= posts.find(value_was, uuid: true) + posts.find(value_was, uuid: true) if value_was.present? end def related_posts? diff --git a/app/models/metadata_has_and_belongs_to_many.rb b/app/models/metadata_has_and_belongs_to_many.rb index f14827e..2c4f3d4 100644 --- a/app/models/metadata_has_and_belongs_to_many.rb +++ b/app/models/metadata_has_and_belongs_to_many.rb @@ -18,6 +18,7 @@ class MetadataHasAndBelongsToMany < MetadataHasMany # # Buscamos en belongs_to la relación local, si se eliminó hay que # quitarla de la relación remota, sino hay que agregarla. + # def save # XXX: No usamos super self[:value] = sanitize value @@ -25,27 +26,21 @@ class MetadataHasAndBelongsToMany < MetadataHasMany return true unless changed? return true unless inverse? + # XXX: Usamos asignación para aprovechar value= que setea el valor + # anterior en @value_was (had_many - has_many).each do |remove| - remove[inverse]&.value&.delete post.uuid.value + remove[inverse].value = remove[inverse].value.reject do |rej| + rej == post.uuid.value + end end (has_many - had_many).each do |add| next unless add[inverse] next if add[inverse].value.include? post.uuid.value - add[inverse].value << post.uuid.value + add[inverse].value = (add[inverse].value.dup << post.uuid.value) end true end - - private - - # Igual que en MetadataRelatedPosts - # TODO: Mover a un módulo - def sanitize(uuid) - super(uuid.map do |u| - u.to_s.gsub(/[^a-f0-9\-]/i, '') - end) - end end diff --git a/app/models/metadata_has_many.rb b/app/models/metadata_has_many.rb index 6135401..a24a5f1 100644 --- a/app/models/metadata_has_many.rb +++ b/app/models/metadata_has_many.rb @@ -6,14 +6,6 @@ # Localmente tenemos un Array de UUIDs. Remotamente tenemos una String # apuntando a un Post, que se mantiene actualizado como el actual. class MetadataHasMany < MetadataRelatedPosts - # Invalidar la relación anterior - def value_was=(new_value) - @had_many = nil - @has_many = nil - - super(new_value) - end - def validate super @@ -24,14 +16,16 @@ class MetadataHasMany < MetadataRelatedPosts # Todos los Post relacionados def has_many - @has_many ||= posts.where(uuid: value) + return default_value if value.blank? + + posts.where(uuid: value) end # La relación anterior def had_many - return [] if value_was.blank? + return default_value if value_was.blank? - @had_many ||= posts.where(uuid: value_was) + posts.where(uuid: value_was) end def inverse? diff --git a/app/models/metadata_related_posts.rb b/app/models/metadata_related_posts.rb index 4c022ff..af91c28 100644 --- a/app/models/metadata_related_posts.rb +++ b/app/models/metadata_related_posts.rb @@ -30,7 +30,7 @@ class MetadataRelatedPosts < MetadataArray # Obtiene todos los posts y opcionalmente los filtra def posts - @posts ||= site.posts(lang: lang).where(**filter) + site.posts(lang: lang).where(**filter) end def title(post) diff --git a/app/models/post.rb b/app/models/post.rb index a64bd55..19eda8d 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -238,12 +238,14 @@ class Post template = public_send attr unless template.front_matter? - body += "\n\n" + body += "\n\n" if body.present? body += template.value next end - next if template.empty? + # Queremos mantener los Array en el resultado final para que + # siempre respondan a {% for %} en Liquid. + next if template.empty? && !template.value.is_a?(Array) [attr.to_s, template.value] end.compact.to_h @@ -301,6 +303,7 @@ class Post end # Vuelve a leer el post para tomar los cambios + document.reset read written? diff --git a/app/models/post_relation.rb b/app/models/post_relation.rb index 850a83d..531d3cc 100644 --- a/app/models/post_relation.rb +++ b/app/models/post_relation.rb @@ -93,8 +93,7 @@ class PostRelation < Array def where(**args) return self if args.empty? - @where ||= {} - @where[args.hash.to_s] ||= begin + begin PostRelation.new(site: site, lang: lang).concat(select do |post| result = args.map do |attr, value| next unless post.attribute?(attr) diff --git a/config/initializers/core_extensions.rb b/config/initializers/core_extensions.rb index 3f4de88..acc4761 100644 --- a/config/initializers/core_extensions.rb +++ b/config/initializers/core_extensions.rb @@ -73,6 +73,15 @@ module Jekyll Document.class_eval do alias_method :read!, :read def read; end + + # Permitir restablecer el documento sin crear uno nuevo + def reset + @path = @extname = @has_yaml_header = @relative_path = nil + @basename_without_ext = @data = @basename = nil + @renderer = @url_placeholders = @url = nil + @to_liquid = @write_p = @excerpt_separator = @id = nil + @related_posts = @cleaned_relative_path = self.content = nil + end end # Prevenir la instanciación de Time diff --git a/test/fixtures/site_with_relationships/README.md b/test/fixtures/site_with_relationships/README.md new file mode 100644 index 0000000..89b5b89 --- /dev/null +++ b/test/fixtures/site_with_relationships/README.md @@ -0,0 +1,2 @@ +This is site where posts can have many authors and viceversa and posts +can be replies to others. diff --git a/test/fixtures/site_with_relationships/_config.yml b/test/fixtures/site_with_relationships/_config.yml new file mode 100644 index 0000000..da2d25c --- /dev/null +++ b/test/fixtures/site_with_relationships/_config.yml @@ -0,0 +1,2 @@ +locales: +- en diff --git a/test/fixtures/site_with_relationships/_data/layouts/author.yml b/test/fixtures/site_with_relationships/_data/layouts/author.yml new file mode 100644 index 0000000..afe620e --- /dev/null +++ b/test/fixtures/site_with_relationships/_data/layouts/author.yml @@ -0,0 +1,9 @@ +--- +title: + type: 'string' + required: true +posts: + type: 'has_and_belongs_to_many' + inverse: 'authors' + filter: + layout: 'post' diff --git a/test/fixtures/site_with_relationships/_data/layouts/post.yml b/test/fixtures/site_with_relationships/_data/layouts/post.yml new file mode 100644 index 0000000..c98baf7 --- /dev/null +++ b/test/fixtures/site_with_relationships/_data/layouts/post.yml @@ -0,0 +1,23 @@ +--- +title: + type: 'string' + required: true +authors: + type: 'has_and_belongs_to_many' + inverse: 'posts' + filter: + layout: 'author' +posts: + type: 'has_many' + inverse: 'in_reply_to' + filter: + layout: 'post' +in_reply_to: + type: 'belongs_to' + inverse: 'posts' + filter: + layout: 'post' +recommended_posts: + type: 'related_posts' + filter: + layout: 'post' diff --git a/test/fixtures/site_with_relationships/_en/.keep b/test/fixtures/site_with_relationships/_en/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/site_with_relationships/_posts b/test/fixtures/site_with_relationships/_posts new file mode 120000 index 0000000..3da1d67 --- /dev/null +++ b/test/fixtures/site_with_relationships/_posts @@ -0,0 +1 @@ +_en \ No newline at end of file diff --git a/test/models/metadata_belongs_to_test.rb b/test/models/metadata_belongs_to_test.rb new file mode 100644 index 0000000..09b9714 --- /dev/null +++ b/test/models/metadata_belongs_to_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataBelongsToTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value) + + assert_equal post, reply.in_reply_to.belongs_to + assert_includes post.posts.has_many, reply + + assert post.save + + assert_equal reply.document.data['in_reply_to'], post.document.data['uuid'] + assert_includes post.document.data['posts'], reply.document.data['uuid'] + end + + test 'se puede eliminar la relación' do + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post.uuid.value) + + reply.in_reply_to.value = '' + assert reply.save + + assert_not_equal post, reply.in_reply_to.belongs_to + assert_equal post, reply.in_reply_to.belonged_to + assert_nil reply.in_reply_to.belongs_to + assert_not_includes post.posts.has_many, reply + + assert post.save + + assert_nil reply.document.data['in_reply_to'] + assert_not_includes post.document.data['posts'], reply.document.data['uuid'] + end + + test 'se puede cambiar la relación' do + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + reply = @site.posts.create(layout: :post, title: SecureRandom.hex, in_reply_to: post1.uuid.value) + + reply.in_reply_to.value = post2.uuid.value + assert reply.save + + assert_not_equal post1, reply.in_reply_to.belongs_to + assert_equal post1, reply.in_reply_to.belonged_to + assert_not_includes post1.posts.has_many, reply + + assert_equal post2, reply.in_reply_to.belongs_to + assert_includes post2.posts.has_many, reply + + assert post1.save + assert post2.save + + assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to'] + assert_includes post2.document.data['posts'], reply.document.data['uuid'] + assert_not_includes post1.document.data['posts'], reply.document.data['uuid'] + end +end diff --git a/test/models/metadata_has_and_belongs_to_many_test.rb b/test/models/metadata_has_and_belongs_to_many_test.rb new file mode 100644 index 0000000..4887a96 --- /dev/null +++ b/test/models/metadata_has_and_belongs_to_many_test.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataHasAndBelongsManyTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex) + + post.authors.value = [author.uuid.value] + assert post.save + + assert_includes author.posts.has_many, post + assert_includes post.authors.has_many, author + + assert author.save + + assert_includes author.document.data['posts'], post.document.data['uuid'] + assert_includes post.document.data['authors'], author.document.data['uuid'] + end + + test 'se puede eliminar la relación' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value]) + + assert_includes post.authors.value, author.uuid.value + assert_includes author.posts.value, post.uuid.value + + post.authors.value = [] + assert post.save + + assert_not_includes author.posts.has_many, post + assert_not_includes post.authors.has_many, author + + assert_includes author.posts.had_many, post + assert_includes post.authors.had_many, author + + assert author.save + + assert_not_includes author.document.data['posts'], post.document.data['uuid'] + assert_not_includes post.document.data['authors'], author.document.data['uuid'] + end + + test 'se puede cambiar la relación' do + author = @site.posts.create(layout: :author, title: SecureRandom.hex) + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, authors: [author.uuid.value]) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + + author.posts.value = [post2.uuid.value] + assert author.save + + assert_not_includes author.posts.has_many, post1 + assert_not_includes post1.authors.has_many, author + + assert_includes author.posts.had_many, post1 + assert_includes post1.authors.had_many, author + + assert_not_includes author.posts.had_many, post2 + assert_not_includes post2.authors.had_many, author + + assert_includes author.posts.has_many, post2 + assert_includes post2.authors.has_many, author + + assert post1.save + assert post2.save + + assert_not_includes author.document.data['posts'], post1.document.data['uuid'] + assert_not_includes post1.document.data['authors'], author.document.data['uuid'] + + assert_includes author.document.data['posts'], post2.document.data['uuid'] + assert_includes post2.document.data['authors'], author.document.data['uuid'] + end +end diff --git a/test/models/metadata_has_many_test.rb b/test/models/metadata_has_many_test.rb new file mode 100644 index 0000000..38b4d46 --- /dev/null +++ b/test/models/metadata_has_many_test.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'test_helper' +require_relative 'metadata_test' + +class MetadataHasManyTest < ActiveSupport::TestCase + include MetadataTest + + test 'se pueden relacionar artículos' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + + assert_equal post, reply.in_reply_to.belongs_to + assert_includes post.posts.has_many, reply + + assert reply.save + + assert_equal reply.document.data['in_reply_to'], post.document.data['uuid'] + assert_includes post.document.data['posts'], reply.document.data['uuid'] + end + + test 'se puede eliminar la relación' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + + post.posts.value = [] + assert post.save + + assert_not_equal post, reply.in_reply_to.belongs_to + assert_equal post, reply.in_reply_to.belonged_to + assert_nil reply.in_reply_to.belongs_to + assert_not_includes post.posts.has_many, reply + + assert reply.save + + assert_nil reply.document.data['in_reply_to'] + assert_not_includes post.document.data['posts'], reply.document.data['uuid'] + end + + test 'se puede cambiar la relación' do + reply = @site.posts.create(layout: :post, title: SecureRandom.hex) + post1 = @site.posts.create(layout: :post, title: SecureRandom.hex, posts: [reply.uuid.value]) + post2 = @site.posts.create(layout: :post, title: SecureRandom.hex) + + reply.in_reply_to.value = post2.uuid.value + assert reply.save + + assert_not_equal post1, reply.in_reply_to.belongs_to + assert_equal post1, reply.in_reply_to.belonged_to + assert_not_includes post1.posts.has_many, reply + + assert_equal post2, reply.in_reply_to.belongs_to + assert_includes post2.posts.has_many, reply + + assert post1.save + assert post2.save + + assert_equal post2.document.data['uuid'], reply.document.data['in_reply_to'] + assert_includes post2.document.data['posts'], reply.document.data['uuid'] + assert_not_includes post1.document.data['posts'], reply.document.data['uuid'] + end +end diff --git a/test/models/metadata_test.rb b/test/models/metadata_test.rb new file mode 100644 index 0000000..24d955a --- /dev/null +++ b/test/models/metadata_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module MetadataTest + extend ActiveSupport::Concern + + included do + setup do + name = SecureRandom.hex + # TODO: Poder cambiar el nombre + FileUtils.cp_r(Rails.root.join('test', 'fixtures', 'site_with_relationships'), Rails.root.join('_sites', name)) + + @site = create :site, name: name + end + + teardown do + @site&.destroy + end + end +end