5
0
Fork 0
mirror of https://0xacab.org/sutty/sutty synced 2024-11-26 01:16:23 +00:00

crear artículos

This commit is contained in:
f 2019-08-13 20:33:57 -03:00
parent cd933c7b38
commit 9469cb41e6
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
15 changed files with 175 additions and 79 deletions

View file

@ -4,24 +4,16 @@ AllCops:
Style/AsciiComments: Style/AsciiComments:
Enabled: false Enabled: false
# Sólo existe para molestarnos (?)
Metrics/AbcSize:
Enabled: false
Metrics/LineLength: Metrics/LineLength:
Exclude: Exclude:
- 'db/schema.rb' - 'db/schema.rb'
- 'db/migrate/*.rb' - 'db/migrate/*.rb'
- 'app/models/site.rb' - 'app/models/site.rb'
Metrics/AbcSize:
Exclude:
- 'db/schema.rb'
- 'db/migrate/*.rb'
- 'app/models/site.rb'
- 'app/controllers/sites_controller.rb'
- 'app/controllers/posts_controller.rb'
- 'app/controllers/invitadxs_controller.rb'
- 'app/controllers/i18n_controller.rb'
- 'app/controllers/usuaries_controller.rb'
- 'app/controllers/collaborations_controller.rb'
Metrics/MethodLength: Metrics/MethodLength:
Exclude: Exclude:
- 'db/schema.rb' - 'db/schema.rb'

View file

@ -41,39 +41,13 @@ class PostsController < ApplicationController
def create def create
authorize Post authorize Post
@site = find_site @site = find_site
@lang = find_lang(@site) service = PostService.new(site: @site,
@template = find_template(@site) usuarie: current_usuarie,
@post = Post.new(site: @site, lang: @lang, template: @template) params: params)
@post.update_attributes(repair_nested_params(post_params))
# El post se guarda como incompleto si se envió con "guardar para if service.create.persisted?
# después" redirect_to site_posts_path(@site)
@post.update_attributes(incomplete:
params[:commit_incomplete].present?)
# Las usuarias pueden especificar una autora, de la contrario por
# defecto es la usuaria actual
if @site.usuarie? current_usuarie
@post.update_attributes(author: params[:post][:author])
else else
# Todo lo que crean les invitades es borrador
@post.update_attributes(draft: true)
end
unless @post.author
@post.update_attributes(author:
current_user.username)
end
if @post.save
if @post.incomplete?
flash[:info] = @site.config.dig('incomplete')
else
flash[:success] = @site.config.dig('thanks')
end
redirect_to site_posts_path(@site, lang: @lang)
else
@invalid = true
render 'posts/new' render 'posts/new'
end end
end end
@ -129,26 +103,4 @@ class PostsController < ApplicationController
redirect_to site_posts_path(@site, category: session[:category], redirect_to site_posts_path(@site, category: session[:category],
lang: @lang) lang: @lang)
end end
private
# Solo permitir cambiar estos atributos de cada articulo
def post_params
params.require(:post).permit(@post.template_params)
end
# https://gist.github.com/bloudermilk/2884947#gistcomment-1915521
def repair_nested_params(obj)
obj.each do |key, value|
if value.is_a?(ActionController::Parameters) || value.is_a?(Hash)
# If any non-integer keys
if value.keys.find { |k, _| k =~ /\D/ }
repair_nested_params(value)
else
obj[key] = value.values
obj[key].each { |h| repair_nested_params(h) }
end
end
end
end
end end

View file

@ -6,4 +6,8 @@ class MetadataArray < MetadataTemplate
def default_value def default_value
[] []
end end
def to_param
{ name => [] }
end
end end

View file

@ -14,6 +14,10 @@ class MetadataContent < MetadataTemplate
sanitize_options) sanitize_options)
end end
def front_matter?
false
end
private private
# Etiquetas y atributos HTML a permitir # Etiquetas y atributos HTML a permitir

View file

@ -10,4 +10,9 @@ class MetadataDocumentDate < MetadataTemplate
def value def value
self[:value] || document.date || default_value self[:value] || document.date || default_value
end end
def value=(date)
date = date.to_time if date.is_a? String
super(date)
end
end end

View file

@ -10,4 +10,8 @@ class MetadataImage < MetadataTemplate
def empty? def empty?
value == default_value value == default_value
end end
def to_param
{ name => %i[description path] }
end
end end

View file

@ -3,6 +3,8 @@
# Representa la plantilla de un campo en los metadatos del artículo # Representa la plantilla de un campo en los metadatos del artículo
# #
# TODO: Validar el tipo de valor pasado a value= según el :type # TODO: Validar el tipo de valor pasado a value= según el :type
#
# rubocop:disable Metrics/BlockLength
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
:value, :help, :required, :errors, :post, :value, :help, :required, :errors, :post,
:layout, keyword_init: true) do :layout, keyword_init: true) do
@ -40,6 +42,15 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
errors.empty? errors.empty?
end end
def to_param
name
end
# Decide si el metadato se coloca en el front_matter o no
def front_matter?
true
end
private private
# Si es obligatorio no puede estar vacío # Si es obligatorio no puede estar vacío
@ -47,3 +58,4 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
true unless required && empty? true unless required && empty?
end end
end end
# rubocop:enable Metrics/BlockLength

View file

@ -21,7 +21,6 @@ class Post < OpenStruct
# @param document: [Jekyll::Document] el documento leído por Jekyll # @param document: [Jekyll::Document] el documento leído por Jekyll
# @param layout: [Layout] la plantilla # @param layout: [Layout] la plantilla
# #
# rubocop:disable Metrics/AbcSize
def initialize(**args) def initialize(**args)
default_attributes_missing(args) default_attributes_missing(args)
super(args) super(args)
@ -40,6 +39,7 @@ class Post < OpenStruct
post: self, post: self,
site: site, site: site,
name: name, name: name,
value: args[name.to_sym],
layout: layout, layout: layout,
type: template['type'], type: template['type'],
label: template['label'], label: template['label'],
@ -54,7 +54,6 @@ class Post < OpenStruct
# Leer el documento # Leer el documento
read read
end end
# rubocop:enable Metrics/AbcSize
def id def id
path.basename path.basename
@ -105,19 +104,34 @@ class Post < OpenStruct
end end
end end
# Devuelve los strong params para el layout
def params
attributes.map do |attr|
send(attr).to_param
end
end
# Genera el post con metadatos en YAML # Genera el post con metadatos en YAML
# rubocop:disable Metrics/AbcSize
def full_content def full_content
body = ''
yaml = layout.metadata.keys.map(&:to_sym).map do |metadata| yaml = layout.metadata.keys.map(&:to_sym).map do |metadata|
template = send(metadata) template = send(metadata)
{ metadata.to_s => template.value } unless template.empty? unless template.front_matter?
body += "\n\n"
body += template.value
next
end
next if template.empty?
{ metadata.to_s => template.value }
end.compact.inject(:merge) end.compact.inject(:merge)
# Asegurarse que haya un layout # Asegurarse que haya un layout
yaml['layout'] = layout.name.to_s yaml['layout'] = layout.name.to_s
"#{yaml.to_yaml}---\n\n#{content.value}" "#{yaml.to_yaml}---\n\n#{body}"
end end
# Eliminar el artículo del repositorio y de la lista de artículos del # Eliminar el artículo del repositorio y de la lista de artículos del
@ -134,7 +148,6 @@ class Post < OpenStruct
!File.exist?(path.absolute) && !site.posts(lang: lang).include?(self) !File.exist?(path.absolute) && !site.posts(lang: lang).include?(self)
end end
alias destroy! destroy alias destroy! destroy
# rubocop:enable Metrics/AbcSize
# Guarda los cambios # Guarda los cambios
def save def save
@ -199,9 +212,16 @@ class Post < OpenStruct
File.exist?(path.absolute) && full_content == File.read(path.absolute) File.exist?(path.absolute) && full_content == File.read(path.absolute)
end end
def update_attributes(hashable)
hashable.to_hash.each do |name, value|
self[name].value = value
end
save
end
private private
# rubocop:disable Metrics/AbcSize
def new_attribute_was(method) def new_attribute_was(method)
attr_was = (attribute_name(method).to_s + '_was').to_sym attr_was = (attribute_name(method).to_s + '_was').to_sym
return attr_was if singleton_class.method_defined? attr_was return attr_was if singleton_class.method_defined? attr_was
@ -229,7 +249,6 @@ class Post < OpenStruct
(send(name).try(:value) || send(name)) != send(name_was) (send(name).try(:value) || send(name)) != send(name_was)
end end
end end
# rubocop:enable Metrics/AbcSize
# Obtiene el nombre del atributo a partir del nombre del método # Obtiene el nombre del atributo a partir del nombre del método
def attribute_name(attr) def attribute_name(attr)

View file

@ -5,6 +5,8 @@
Site::Writer = Struct.new(:site, :file, :content, keyword_init: true) do Site::Writer = Struct.new(:site, :file, :content, keyword_init: true) do
# TODO: si el archivo está bloqueado, esperar al desbloqueo # TODO: si el archivo está bloqueado, esperar al desbloqueo
def save def save
mkdir_p
File.open(file, File::RDWR | File::CREAT, 0o640) do |f| File.open(file, File::RDWR | File::CREAT, 0o640) do |f|
# Bloquear el archivo para que no sea accedido por otro # Bloquear el archivo para que no sea accedido por otro
# proceso u otre editore # proceso u otre editore
@ -26,4 +28,12 @@ Site::Writer = Struct.new(:site, :file, :content, keyword_init: true) do
def relative_file def relative_file
Pathname.new(file).relative_path_from(Pathname.new(site.path)).to_s Pathname.new(file).relative_path_from(Pathname.new(site.path)).to_s
end end
def dirname
File.dirname file
end
def mkdir_p
FileUtils.mkdir_p dirname
end
end end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
# Este servicio se encarga de crear artículos y guardarlos en git,
# asignándoselos a une usuarie
PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
# Crea un artículo nuevo
#
# @return Post
def create
# TODO: Implementar layout
self.post = site.posts.build
# TODO: No podemos pasar los post_params a build aun porque para
# saber los parámetros tenemos que haber instanciado el post
# primero.
post.update_attributes(post_params) &&
site.repository.commit(file: post.path.absolute,
usuarie: usuarie,
message: I18n.t('post_service.created',
title: post.title.value))
# Devolver el post aunque no se haya salvado para poder rescatar los
# errores
post
end
private
# Solo permitir cambiar estos atributos de cada articulo
def post_params
params.require(:post).permit(post.params)
end
end

View file

@ -25,10 +25,11 @@
-# Dibuja cada atributo -# Dibuja cada atributo
- post.attributes.each do |attribute| - post.attributes.each do |attribute|
- type = post.send(attribute).type - metadata = post.send(attribute)
- type = metadata.type
= render "posts/attributes/#{type}", = render "posts/attributes/#{type}",
post: post, attribute: attribute, post: post, attribute: attribute,
metadata: post.send(attribute) metadata: metadata
-# Botones de guardado -# Botones de guardado
= render 'posts/submit', site: site = render 'posts/submit', site: site

View file

@ -97,12 +97,9 @@ Al instanciar un `Post`, se pasan el sitio y la plantilla por defecto.
* Reimplementar glosario (se crea un artículo por cada categoría * Reimplementar glosario (se crea un artículo por cada categoría
utilizada) utilizada)
* Reimplementar orden de artículos (ver doc) * Reimplementar orden de artículos (ver doc)
* Convertir layout a params
* Reimplementar plantillas
* Reimplementar subida de imagenes/archivos * Reimplementar subida de imagenes/archivos
* Reimplementar campo 'pre' y 'post' en los layouts.yml * Reimplementar campo 'pre' y 'post' en los layouts.yml
* Implementar autoría como un array * Implementar autoría como un array
* Reimplementar draft e incomplete (por qué eran distintos?) * Reimplementar draft e incomplete (por qué eran distintos?)
* Convertir idiomas disponibles a pestañas? * Convertir idiomas disponibles a pestañas?
* Implementar traducciones sin adivinar. Vincular artículos entre sí * Implementar traducciones sin adivinar. Vincular artículos entre sí

View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
require 'test_helper'
class PostsControllerTest < ActionDispatch::IntegrationTest
setup do
@rol = create :rol
@site = @rol.site
@usuarie = @rol.usuarie
@post = @site.posts.build(title: SecureRandom.hex)
@post.save
@authorization = {
Authorization: ActionController::HttpAuthentication::Basic
.encode_credentials(@usuarie.email, @usuarie.password)
}
end
teardown do
@site.destroy
end
test 'se pueden ver' do
get site_posts_url(@site), headers: @authorization
assert_match @site.name, response.body
assert_match @post.title.value, response.body
end
test 'se pueden crear nuevos' do
title = SecureRandom.hex
post site_posts_url(@site), headers: @authorization,
params: {
post: {
title: title,
date: 2.days.ago.strftime('%F')
}
}
# TODO: implementar reload?
site = Site.find(@site.id)
site.read
new_post = site.posts.first
assert_equal 302, response.status
# XXX: No usamos follow_redirect! porque pierde la autenticación
get site_posts_url(@site), headers: @authorization
assert_match new_post.title.value, response.body
assert_equal title, new_post.title.value
assert_equal I18n.t('post_service.created', title: new_post.title.value),
@site.repository.rugged.head.target.message
end
end

View file

@ -5,5 +5,6 @@ FactoryBot.define do
email { SecureRandom.hex + '@sutty.nl' } email { SecureRandom.hex + '@sutty.nl' }
password { SecureRandom.hex } password { SecureRandom.hex }
confirmed_at { Date.today } confirmed_at { Date.today }
lang { I18n.default_locale.to_s }
end end
end end

View file

@ -127,7 +127,7 @@ class PostTest < ActiveSupport::TestCase
end end
test 'al cambiar slug o fecha cambia el archivo de ubicacion' do test 'al cambiar slug o fecha cambia el archivo de ubicacion' do
hoy = Date.today.to_time hoy = 2.days.ago.to_time
path_was = @post.path.absolute path_was = @post.path.absolute
@post.slug.value = 'test' @post.slug.value = 'test'
@post.date.value = hoy @post.date.value = hoy
@ -168,4 +168,12 @@ class PostTest < ActiveSupport::TestCase
assert post.save assert post.save
assert File.exist?(post.path.absolute) assert File.exist?(post.path.absolute)
end end
test 'se pueden inicializar con valores' do
post = @site.posts.build(title: 'test', content: 'test')
assert_equal 'test', post.title.value
assert_equal 'test', post.content.value
assert post.save
end
end end