Microservicio de noticias en Rails 5. Parte 2
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:
- El namespace admin lo estoy colocando antes, pues aquí usamos un scope que sirve como variable, si lo colocamos después Rails considerará que admin es un canal.
- Siempre estoy usando los uuid para identificar los elementos, pese a que internamente sigo trabajando con los ids. Esto es básicamente porque el soporte para conectar los modelos mediante UUID no está tan limpio aún.
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.