mirror of
https://0xacab.org/sutty/sutty
synced 2024-11-14 22:51:41 +00:00
crear artículos
This commit is contained in:
parent
cd933c7b38
commit
9469cb41e6
15 changed files with 175 additions and 79 deletions
16
.rubocop.yml
16
.rubocop.yml
|
@ -4,24 +4,16 @@ AllCops:
|
|||
Style/AsciiComments:
|
||||
Enabled: false
|
||||
|
||||
# Sólo existe para molestarnos (?)
|
||||
Metrics/AbcSize:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
- 'db/migrate/*.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:
|
||||
Exclude:
|
||||
- 'db/schema.rb'
|
||||
|
|
|
@ -41,39 +41,13 @@ class PostsController < ApplicationController
|
|||
def create
|
||||
authorize Post
|
||||
@site = find_site
|
||||
@lang = find_lang(@site)
|
||||
@template = find_template(@site)
|
||||
@post = Post.new(site: @site, lang: @lang, template: @template)
|
||||
@post.update_attributes(repair_nested_params(post_params))
|
||||
service = PostService.new(site: @site,
|
||||
usuarie: current_usuarie,
|
||||
params: params)
|
||||
|
||||
# El post se guarda como incompleto si se envió con "guardar para
|
||||
# después"
|
||||
@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])
|
||||
if service.create.persisted?
|
||||
redirect_to site_posts_path(@site)
|
||||
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'
|
||||
end
|
||||
end
|
||||
|
@ -129,26 +103,4 @@ class PostsController < ApplicationController
|
|||
redirect_to site_posts_path(@site, category: session[:category],
|
||||
lang: @lang)
|
||||
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
|
||||
|
|
|
@ -6,4 +6,8 @@ class MetadataArray < MetadataTemplate
|
|||
def default_value
|
||||
[]
|
||||
end
|
||||
|
||||
def to_param
|
||||
{ name => [] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,10 @@ class MetadataContent < MetadataTemplate
|
|||
sanitize_options)
|
||||
end
|
||||
|
||||
def front_matter?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Etiquetas y atributos HTML a permitir
|
||||
|
|
|
@ -10,4 +10,9 @@ class MetadataDocumentDate < MetadataTemplate
|
|||
def value
|
||||
self[:value] || document.date || default_value
|
||||
end
|
||||
|
||||
def value=(date)
|
||||
date = date.to_time if date.is_a? String
|
||||
super(date)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,4 +10,8 @@ class MetadataImage < MetadataTemplate
|
|||
def empty?
|
||||
value == default_value
|
||||
end
|
||||
|
||||
def to_param
|
||||
{ name => %i[description path] }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
# 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
|
||||
#
|
||||
# rubocop:disable Metrics/BlockLength
|
||||
MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
||||
:value, :help, :required, :errors, :post,
|
||||
:layout, keyword_init: true) do
|
||||
|
@ -40,6 +42,15 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
|||
errors.empty?
|
||||
end
|
||||
|
||||
def to_param
|
||||
name
|
||||
end
|
||||
|
||||
# Decide si el metadato se coloca en el front_matter o no
|
||||
def front_matter?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Si es obligatorio no puede estar vacío
|
||||
|
@ -47,3 +58,4 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type,
|
|||
true unless required && empty?
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
|
|
|
@ -21,7 +21,6 @@ class Post < OpenStruct
|
|||
# @param document: [Jekyll::Document] el documento leído por Jekyll
|
||||
# @param layout: [Layout] la plantilla
|
||||
#
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def initialize(**args)
|
||||
default_attributes_missing(args)
|
||||
super(args)
|
||||
|
@ -40,6 +39,7 @@ class Post < OpenStruct
|
|||
post: self,
|
||||
site: site,
|
||||
name: name,
|
||||
value: args[name.to_sym],
|
||||
layout: layout,
|
||||
type: template['type'],
|
||||
label: template['label'],
|
||||
|
@ -54,7 +54,6 @@ class Post < OpenStruct
|
|||
# Leer el documento
|
||||
read
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def id
|
||||
path.basename
|
||||
|
@ -105,19 +104,34 @@ class Post < OpenStruct
|
|||
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
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def full_content
|
||||
body = ''
|
||||
yaml = layout.metadata.keys.map(&:to_sym).map do |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)
|
||||
|
||||
# Asegurarse que haya un layout
|
||||
yaml['layout'] = layout.name.to_s
|
||||
|
||||
"#{yaml.to_yaml}---\n\n#{content.value}"
|
||||
"#{yaml.to_yaml}---\n\n#{body}"
|
||||
end
|
||||
|
||||
# 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)
|
||||
end
|
||||
alias destroy! destroy
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# Guarda los cambios
|
||||
def save
|
||||
|
@ -199,9 +212,16 @@ class Post < OpenStruct
|
|||
File.exist?(path.absolute) && full_content == File.read(path.absolute)
|
||||
end
|
||||
|
||||
def update_attributes(hashable)
|
||||
hashable.to_hash.each do |name, value|
|
||||
self[name].value = value
|
||||
end
|
||||
|
||||
save
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def new_attribute_was(method)
|
||||
attr_was = (attribute_name(method).to_s + '_was').to_sym
|
||||
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)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# Obtiene el nombre del atributo a partir del nombre del método
|
||||
def attribute_name(attr)
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
Site::Writer = Struct.new(:site, :file, :content, keyword_init: true) do
|
||||
# TODO: si el archivo está bloqueado, esperar al desbloqueo
|
||||
def save
|
||||
mkdir_p
|
||||
|
||||
File.open(file, File::RDWR | File::CREAT, 0o640) do |f|
|
||||
# Bloquear el archivo para que no sea accedido por otro
|
||||
# proceso u otre editore
|
||||
|
@ -26,4 +28,12 @@ Site::Writer = Struct.new(:site, :file, :content, keyword_init: true) do
|
|||
def relative_file
|
||||
Pathname.new(file).relative_path_from(Pathname.new(site.path)).to_s
|
||||
end
|
||||
|
||||
def dirname
|
||||
File.dirname file
|
||||
end
|
||||
|
||||
def mkdir_p
|
||||
FileUtils.mkdir_p dirname
|
||||
end
|
||||
end
|
||||
|
|
32
app/services/post_service.rb
Normal file
32
app/services/post_service.rb
Normal 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
|
|
@ -25,10 +25,11 @@
|
|||
|
||||
-# Dibuja cada atributo
|
||||
- post.attributes.each do |attribute|
|
||||
- type = post.send(attribute).type
|
||||
- metadata = post.send(attribute)
|
||||
- type = metadata.type
|
||||
= render "posts/attributes/#{type}",
|
||||
post: post, attribute: attribute,
|
||||
metadata: post.send(attribute)
|
||||
metadata: metadata
|
||||
|
||||
-# Botones de guardado
|
||||
= render 'posts/submit', site: site
|
||||
|
|
|
@ -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
|
||||
utilizada)
|
||||
* Reimplementar orden de artículos (ver doc)
|
||||
* Convertir layout a params
|
||||
* Reimplementar plantillas
|
||||
* Reimplementar subida de imagenes/archivos
|
||||
* Reimplementar campo 'pre' y 'post' en los layouts.yml
|
||||
* Implementar autoría como un array
|
||||
* Reimplementar draft e incomplete (por qué eran distintos?)
|
||||
|
||||
* Convertir idiomas disponibles a pestañas?
|
||||
* Implementar traducciones sin adivinar. Vincular artículos entre sí
|
||||
|
|
55
test/controllers/posts_controller_test.rb
Normal file
55
test/controllers/posts_controller_test.rb
Normal 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
|
|
@ -5,5 +5,6 @@ FactoryBot.define do
|
|||
email { SecureRandom.hex + '@sutty.nl' }
|
||||
password { SecureRandom.hex }
|
||||
confirmed_at { Date.today }
|
||||
lang { I18n.default_locale.to_s }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -127,7 +127,7 @@ class PostTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
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
|
||||
@post.slug.value = 'test'
|
||||
@post.date.value = hoy
|
||||
|
@ -168,4 +168,12 @@ class PostTest < ActiveSupport::TestCase
|
|||
assert post.save
|
||||
assert File.exist?(post.path.absolute)
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue