Crear API/WEB/CMS utilizando Ruby on Rails

| 2020-03-5 | No hay comentarios »

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).

WEB

Se utilizarán MariaDB y Redis en la capa de bases de datos, como así también Bootstrap como framework CSS.

CMS

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:

<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."/>
<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
API

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.

Acerca del autor: Rodrigo Paszniuk

Ingeniero Informático, amante de la tecnología, la música, el ciclismo y aprender cosas nuevas.

Posts Relacionados

  • Guía para crear una replicación en MariaDB
  • Usar Replicas de Bases de Datos en Ruby on Rails con la gema Octopus
  • Recursos – Ruby on Rails
  • Seguridad – Ruby on Rails



SEGUÍNOS EN FACEBOOK


GITHUB