Microservicio de noticias en Rails 5. Parte 2

(es)

rubyrails

Esta es la continuación del tutorial, si te faltó leerla, puedes ver la parte 1.

Ahora ya que podemos ver resultados en el sistema, haremos un administrador para que se puedan ingresar noticias en él.

Primero haremos el namespace de administración.

routes.rb

Rails.application.routes.draw do
namespace :admin do
resources :posts, param: :uuid do
member do
put :publish
put :unpublish
end
end
end

scope ':channel' do
resources :posts, only: [:show, :index], param: :uuid
end
end

Dos cosas importantes:

Podría trabajar en el administrador con los ids en vez de los uuid, pero esto hace más fácil que si tienes un problema en la vista de lectura, puedas encontrar la noticia más rápidamente.

Ahora debemos crear el controlador rails g controller admin/posts

Pero antes de implementar el controlador, agregaremos otro, un BaseController, que nos permitirá colocarle reglas especiales a los controladores de Admin (por ejemplo, autenticación). Teniendo controladores base por namespace nos permitirá tener distintas reglas en cada uno de ellos, lo que puede ser muy cómodo.

admin/base_controller.rb

class Admin::BaseController < ApplicationController
end

admin/posts_controller.rb

class Admin::PostsController < Admin::BaseController
before_action :set_post, only: [:show, :edit, :update, :destroy, :publish, :unpublish]
def index
@posts = Post.incremental
end

def show
end

def new
@post = Post.new
end

def create
@post = Post.create post_params
show_post
end

def edit
end

def update
@post = Post.update post_params
show_post
end

def destroy
@post.destroy
end

def publish
@post.publish!
show_post
end

def unpublish
@post.unpublish!
show_post
end

private

def show_post
redirect_to admin_post_path uuid: @post.uuid and return
end

def post_params
params.require(:post).permit(:title, :message, :channel, :url)
end

def set_post
@post = Post.find_by_uuid params[:uuid]
end
end

Obviamente, con tanto escribir olvidé la estructura de Post, pero en vez de ir a revisar al schema pueda preguntar en la consola.

show-model Post

Post
id: integer
channel: string
uuid: uuid
options: json
message: text
title: text
published_at: datetime
meta: json
aasm_state: string
created_at: datetime
updated_at: datetime
url: string

También podemos fijarnos que el modelo de Post tiene unos metodos adicionales: publish! y unpublish! asi que debemos implementarlos y como estamos trabajando con UUID sería bueno asegurarnos que exista al crearse el objeto.

class Post < ApplicationRecord
before_validation :check_uuid
validates :uuid, uniqueness: true, presence: true

scope :of_channel, -> (channel) { where(channel: channel) }
scope :published, -> { where.not(published_at: nil) }
scope :incremental, -> { order(published_at: :desc) }

def publish!
update published_at: Time.zone.now
end

def unpublish!
update published_at: nil
end

private

def check_uuid
return unless self.uuid.nil?
uuid = SecureRandom.uuid
while self.class.where(uuid: uuid).count > 0
uuid = SecureRandom.uuid
end
self.uuid = uuid
end
end

Y ahora finalmente agregamos los templates.

index.html.erb

<h1>Posts</h1>
<ul class="nms-post-list">
<%= render partial: "post", collection: @posts %>
</ul>
<ul class="nms-posts-actions">
<li><%= link_to "New post", new_admin_post_path %></li>
</ul>

_post.html.erb

<li class="nms-post-list-item">
<%= link_to admin_post_path(uuid: post.uuid) do %>
<%= post.title %>
<% end %>
</li>

show.html.erb

<article class="nms-post" data-uuid="<%= @post.uuid %>">
<h1 class="nms-post-title"><%= @post.title %></h1>
<p class="nms-post-header">
<small class="nms-post-channel" data-value="<%= @post.channel %>">Channel: <%= @post.channel %></small>
<% if @post.published_at %>
<br />
<small class="nms-post-publication-date" data-value="<%= @post.published_at %>">Published: <%= l @post.published_at %></small>
<% end %>
</p>
<p class="nms-post-message"><%= @post.message %></p>
</article>
<ul class="nms-post-actions">
<li><%= link_to 'Edit', edit_admin_post_path(uuid: @post.uuid) %></li>
<% if @post.published_at.nil? %>
<li><%= link_to 'Publish', publish_admin_post_path(uuid: @post.uuid), method: :put %></li>
<% else %>
<li><%= link_to 'Unpublish', unpublish_admin_post_path(uuid: @post.uuid), method: :put %></li>
<% end %>
<li><%= link_to 'All posts', admin_posts_path %></li>
</ul>

_form.html.erb

<%= form_for [:admin, post] do |p| %>
<%= p.label :channel %>
<%= p.text_field :channel %> <br />
<%= p.label :title %>
<%= p.text_field :title %> <br />
<%= p.label :message %>
<%= p.text_area :message %> <br />
<%= p.label :url %>
<%= p.text_field :url %> <br />
<%= submit_tag %>
<% end %>

edit.html.erb

<%= render partial: "form", locals: {post: @post} %>

<ul class="nms-edit-post-options">
<li><%= link_to 'Back', admin_post_path(uuid: @post.uuid) %></li>
<li><%= link_to 'All posts', admin_posts_path %></li>
</ul>

new.html.erb

<%= render partial: "form", locals: {post: Post.new } %>

<ul class="nms-new-post-options">
<li><%= link_to 'Back', admin_posts_path %></li>
</ul>

La siguiente parte del tutorial considera el aprovechar un nuevo feature de Rails 5: ActionCable, luego seguiremos con cosas más convencionales: agregarle seguridad al sistema y en la última etapa le agregaremos un poco de estilo para que no se vea tan simplón.