Ejemplo de la utilización de Ruby on Rails 6 para crear un web, un api y un cms (en un mismo proyecto).
La web muestra frases .y categorías (demo), el cms sirve para administrar frases, usuarios, permisos, categorías (demo) y por último el api (demo).
Se utiliza MariaDB y Redis en la capa de bases de datos, como así también Bootstrap como framework CSS.
Otra cosa importante a tener en cuenta es que se utiliza Rubocop para programar limpiamente y de una forma ordenada.
Ruby on Rails 6
Para poder usar el Framework Ruby on Rails 6 se necesita tener instalado el Lenguaje de Programación Ruby 2.6 o superior en nuestro Sistema Operativo.
Recomiendo el uso de RVM para MacOS/Linux y para Windows recomiendo: Ruby+Devkit 2.6.5-1 (x64)
Por más detalles acerca de Ruby/Rails ingresar al siguiente link, ya que hemos creado un manual básico del mismo hace un tiempo atrás.
Bases de datos
En este proyecto se utilizó MariaDB como base de datos principal y Redis como para cachear frases y categorías para así no tener que hacer reiteradas consultas a la base de datos principal. Con esto se obtiene mayor performance en la web y el api.
Instrucciones
Todo el proyecto está en GitHub, y los pasos a seguir son los siguientes:
git clone https://github.com/rpaszniuk/frases480.git
rake app:update:bin
bundle install
Configurar los credentials: bin/rails credentials:edit
secret_key_base:
name: 'Frases 480'
emails:
admin: ''
no_reply: ''
sendgrid:
api_key: ''
sandbox: false
templates:
password_recovery: ''
cookie_domain: ''
web_host: ''
cms_domain: ''
app_secret: ''
redis:
host: 'localhost'
port: 6379
Configurar base de datos (Ejemplo: config/database_sample.yml)
rake db:create
rake db:migrate
rake db:seed
Con eso ya tendrían el proyecto configurado correctamente y para poder visualizar correctamente todo deberán de agregar lo siguiente al archivo hosts:
127.0.0.1 frases480.local
127.0.0.1 cms.frases480.local
127.0.0.1 api.frases480.local
Una vez terminado eso, podrán usar el comando rails s y ver la web y el cms sin problema alguno utilizando esos subdominios que hemos apuntado a 127.0.0.1 (localhost).
Rutas
Rutas (routes.rb) (utilizando namespace y subdominios):
default_cms_actions = [:index, :new, :create, :edit, :update, :destroy]
Rails.application.routes.draw do
namespace :api, path: '', constraints: { subdomain: ['api'] }, defaults: { format: :json } do
namespace :v1 do
resources :phrases, only: [:index, :show]
resources :categories, only: [:index, :show]
namespace :users do
post 'sign-in'
post '', action: :create, as: :create
end
namespace :me do
get '', action: :show, as: :show
put '', action: :update, as: :update
resources :phrases, only: [:index, :create, :show, :destroy, :update]
end
end
end
namespace :cms, path: '', constraints: { subdomain: ['cms'] } do
namespace :sessions, path: '' do
post 'login'
get 'logout'
get 'forgot_password'
get 'sign_up'
post 'forgot_password'
post 'sign_up'
get '/recover_password/:secure_hash', action: :recover_password, as: :recover_password
patch 'change_password'
end
namespace :dashboard do
get :index, as: '/'
end
resources :users, only: default_cms_actions
resources :access_profiles, only: default_cms_actions
resources :phrases, only: default_cms_actions
resources :categories, only: default_cms_actions
namespace :me do
get '', action: :show, as: :show
end
namespace :login do
get '', action: :index, as: :index
end
get '/', action: :index, controller: :login, as: :root
end
namespace :web, path: '', constraints: { subdomain: ['', 'www'] } do
namespace :main, path: '' do
get :acerca, action: :about
get :feed
end
resources :phrases, only: [:show, :index]
resources :categories, only: [:show, :index]
get 'search/:action' => 'searches#:action'
get '/:id', controller: :categories, action: :show, as: :show_category
get '/:id/:phrase', controller: :phrases, action: :show, as: :show_phrase
get '/', action: :index, controller: :main, as: :root
end
end
Como pueden ver se utilizó 3 namespaces diferentes: web, cms y api. De esa forma se puede tener todo dentro de un mismo proyecto, haciendolo más fácil de mantender.
Se provee de un sitemap completo con todas las frases y categorías y un feed RSS. También las URLs generadas son amigables y se crear los meta tags dinámicamente para mejorar el SEO.
Ejemplo del sitemap: https://www.frases480.com/sitemap.xml.gz y ejemplo de URLs que se generan:
- https://www.frases480.com/perdon/perdona-y-perdonate-es-la-unica-forma-de-viajar-sin (una frase dentro de la categoría perdon)
<meta name="description" content="PERDONA y PERDÓNATE. Es la única forma de viajar sin un pesado equipaje por esta hermosa vida."/>
<meta name="og:url" content="https://www.frases480.com/perdon/perdona-y-perdonate-es-la-unica-forma-de-viajar-sin"/>
<meta name="og:title" content="Frase de Perdon"/>
<meta name="og:description" content="PERDONA y PERDÓNATE. Es la única forma de viajar sin un pesado equipaje por esta hermosa vida."/>
- https://www.frases480.com/abundancia (una categoría)
<meta name="description" content="Abundancia"/>
<meta name="og:url" content="https://www.frases480.com/abundancia"/>
<meta name="og:title" content="Abundancia"/>
<meta name="og:description" content="Abundancia"/>
Ejemplo del Feed RSS: https://www.frases480.com/feed?format=rss
Para el API se utilizó active_model_serializers y jbuilder, ejemplo:
class API::V1::UserSerializer < ActiveModel::Serializer
attributes :id, :email, :first_name, :last_name, :time_zone, :status
end
class API::V1::UsersController < ApplicationController
include APIAccessControl
def create
user = User.new(user_sign_up_params)
if user.save
render json: { token: user.auth_token, user: API::V1::UserSerializer.new(user) }, status: :ok
else
render json: { errors: user.errors }, status: :unprocessable_entity
end
end
def sign_in
user_auth = UserAuth.new(user_sign_in_params)
if user_auth.do_sign_in
render json: { token: user_auth.user.auth_token, user: API::V1::UserSerializer.new(user_auth.user) }, status: :ok
else
render json: { errors: user_auth.errors }, status: :unprocessable_entity
end
end
def recover_password
user_auth = UserAuth.new(user_sign_in_params)
user_auth.signing_in = false
if user_auth.do_recover_password
render nothing: true, status: :ok
else
render json: { errors: user_auth.errors }, status: :unprocessable_entity
end
end
private
def user_sign_in_params
params.permit(:email, :password)
end
def user_sign_up_params
params.permit(:first_name, :last_name, :email, :password, :time_zone)
end
end
Para el cms se utilizó un sistema personalizado para autenticar usuarios como así también para controlar permisos, no se utilizó ninguna gema en especial (como devise o cancan).
En este proyecto también se hizo uso de Webpack (para la web) y el conocido Asset Pipeline (para el cms).
Se optó mini magick junto con Active Storage para guardar/procesar imágenes, will paginate para manejar la paginación, sentry para reportar crashes/errores, recaptcha para evitar registros de bots y sendgrid para enviar emails (registros, recuperación de contraseña, etc).
Espero que esto les sirva de mucha utilidad, el objetivo de esto es que puedan ver todo lo que se puede hacer en un proyecto de Rails, que lo considero un framework muy completo y maduro que poco a poco va ganando más popularidad. Ante cualquier duda no duden en preguntar.