Configuración inicial (devise y bootstrap)

  1. Crear un nuevo proyecto:
rails new edificios -d postgresql
  1. Configurar los datos de conexión:
default: &default adapter: postgresql encoding: unicode user: tu_usuario 👈 password: tu_password 👈 # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  1. Crear la base de datos
rails db:create
  1. Añadir Devise al proyecto
bundle add devise
  1. Instalar el generador de Devise
rails g devise:install
  1. Añadir la condiguración de devise:

    • colocar la url por defecto para el envió de correos
    ​​​​# config/environments/development.rb ​​​​config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
    • colocar los mensajes flash en el layouts principal:
    ​​​​<div class="container"> ​​​​ <p class="notice"><%= notice %></p> ​​​​ <%= yield %> ​​​​</div>
  2. Crear los archivos de migración junto con el modelo de User de Devise:

rails g devise User
  1. Creamos las tablas en db ejecutando la migración:
rails db:migrate

Creando usuarios desde consola

rails c
  • CREAR:
# primera forma
user = User.new
user.password = '123456'
user.password_confirmation='123456'
user.email = 'user1@gmail.com'
user.save

# segunda forma
User.create(
    email: 'user2@gmail.com', 
    password: 'abcdefgh',
    password_confirmation: 'abcdefgh'
)
  • LEER:
# Seleccionar todos los registros
User.all
# Seleccionar todos los usuarios que empiezan con u
User.all.select { |m| m.email[0] == 'u' }
  • ACTUALIZAR:
# primera forma
user = User.find_by(email: 'user1@gmail.com')
user.email = 'usuario1@gmail.com'
user.password = '123456'
password_confirmation: '123456'
user.save

# segunda forma
User.update(
    1, # 👈 id del registro que se quiere actualizar
    email: 'userio_1@gmail.com', 
    password: 'abc123456',
    password_confirmation: 'abc123456'
)
  • ELIMINAR
# usando destroy (eliminará el registro de objeto actual de la base de datos y también su registro secundario asociado de la base de datos)
User.first.destroy

# usando delete (solo eliminará el registro de objeto actual de la base de datos, pero no sus registros secundarios asociados de la base de datos)
User.last.delete

Crear landing page

La aplicación debe contar con una landing page estática que muestre el nombre de la
empresa “Inmobiliaria Virtual” con un diseño atractivo. Desde ahí puedo ir a ver tanto el
listado de edificios como el de departamentos.

  1. Entonces creamos un controlador Home con un método index para tener una vista predeterminada:
rails g controller home index
  1. Establecemos la ruta por defecto:
# config/routes.rb Rails.application.routes.draw do devise_for :users root "home#index" end
  1. Correr el servidor de desarrollo:
rails s

Visitamos http://localhost:3000/

  1. Añadimos bootstrap al proyecto vía CDN en el archivo app/views/layouts/application.html.erb:
<!DOCTYPE html>
<html>
  <head>
    <title>Edificios</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" 
          rel="stylesheet" 
          integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" 
          crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
      <p class="notice"><%= notice %></p>
      <%= yield %>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" 
            integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" 
            crossorigin="anonymous"></script>
  </body>
</html>
  1. Creamos un partial en app/views/shared/_navbar.html.erb, y añadimos la navegación usando un componente navbar de bootstrap:
<nav class="navbar navbar-expand-lg bg-light">
  <div class="container">
    <%= link_to("Home", root_path, class: 'navbar-brand') %>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent"
    aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarContent">
      <div class="navbar-nav ms-auto mb-2 mb-lg-0">
        <%= link_to("Edificios", buildings_url, class: 'nav-link') %>
        <%= link_to("Departamentos", apartments_url, class: "nav-link") %>
      <div class="nav-item dropdown">
        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
          <%= user_signed_in?? "Hola 👋 #{current_user.email}" : "Debes registrarte" %>
        </a>
        <div class="dropdown-menu">
          <% if user_signed_in? %>
            <%= link_to 'Cerrar sesión', destroy_user_session_path, class: 'dropdown-item', 'data-turbo-method': :delete %>
          <% else %>
            <%= link_to 'Iniciar sesión', new_user_session_path, class: 'dropdown-item'%>
            <hr class="dropdown-divider">
            <%= link_to 'Registro', new_user_registration_path, class: 'dropdown-item'%>
          <% end %>
        </div>
      </div>
      </div>
    </div>
  </div>
</nav>

Para que los links funcionen, debemos crear los modelos como se explica la sección de Creando los modelos

  1. Y lo renderizamos en el layouts principal (línea 15):
<!DOCTYPE html> <html> <head> <title>Edificios</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> </head> <body class="d-flex flex-column min-vh-100"> <%= render 'shared/navbar' %> <% if flash[:notice] %> <div class="alert alert-<%= flash[:context]? flash[:context] : 'success' %> alert-dismissible fade show" role="alert"> <strong><%= flash[:notice] %></strong> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> <% end %> <%= yield %> <%= render 'shared/footer' %> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/6b8f0c7049.js" crossorigin="anonymous"></script> </body> </html>

Ideas para la landing page

Ya tenemos entonces la navegación ahora trabajaremos en la vista index del home, que es nuestra landing page, podemos crear un partial para crear un hero como primera opción:

<!-- app/views/shared/_hero.html.erb --> <section className="hero" style="background: linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.7)),url(<%= background %>); background-size: cover;" class="min-vh-100 d-flex justify-content-center align-items-center"> <div class="hero__texts"> <% if title || subtitle %> <h2 class="display-1 text-light px-4 fw-bolder" style="backdrop-filter: sepia(90%)"><%= title %></h2> <h4 class="text-warning" style="backdrop-filter: blur(15px)"> <%= defined?(subtitle).nil? ? 'subtitle' : subtitle %> </h4> <%= link_to(link["name"],link["href"], class: "btn btn-lg btn-outline-light rounded-0") %> <% end %> </div> </section>
<%= render 'shared/hero', background:"https://imgclasificados2.emol.com/Proyectos/imagenes/proyecto/PR_FOTO_4349_CAM_Piloto-ComedorLiving_AlTA_01_Post.jpg", title: "Inmobiliaria Virtual", subtitle: "Un concepto diferente para tu estilo de vida", link: {"name" => "Proyectos inmobiliarios", "href" => buildings_url} %>

hero

Por otro lado podemos, crear un simple array con url de imagenes desde el controlador y mostrarlas en un carrusel de bootstrap:

# app/controllers/home_controller.rb class HomeController < ApplicationController def index @images = [ 'https://www.iproyeccion.cl/content/uploads/2021/06/Las-Heras_ppal-7-EI-1920x1080-1-1440x900.jpg', 'https://www.planosdearquitectura.com/wp-content/uploads/2015/08/Dise%C3%B1o-de-sal-estar-moderno-de-departamento.jpg', 'https://p4.wallpaperbetter.com/wallpaper/241/699/670/274-apartment-condo-design-wallpaper-preview.jpg', 'https://www.adondevivir.com/noticias/wp-content/uploads/2016/08/depto-1024x546.jpg' ] end end
<div id="carouselExample" class="carousel slide carousel-fade" data-bs-ride="carousel"> <div class="carousel-inner"> <% @images.each_with_index do |image, index| %> <div class="carousel-item <%= index == 0 ? 'active' : '' %>" style="background-image: linear-gradient(rgba(20,10,30,.3),rgba(105,140,150,.7)), url(<%= image %>); min-height: 100vh; background-size: cover;" data-bs-interval="3000"> </div> <% end %> <div class="position-absolute top-50 z-3 text-center w-100"> <%= link_to('Edificios', buildings_url, class: 'btn btn-lg btn-light rounded-0 me-3') %> <%= link_to('Departamentos', apartments_url, class: 'btn btn-lg btn-outline-light rounded-0') %> </div> </div> </div>

gif carrousel


Creando los modelos

De momento se necesitan dos modelos principales y dos modelos adicionales que debes descubrir en las indicaciones posteriores. Los modelos principales son:

has

BUILDING

id

int

PK

Esta es la llave primaria

name

string

address

string

APARTMENT

id

int

PK

aparment_number

string

building_id

int

FK

Un departamento debe pertenecer a un edificio

Crear el modelo Building (edificio):

rails generate model Building name address

Crear el modelo Apartment (departamento):

rails g model Apartment num_apartment building:references

Nos piden que la aplicación debe contar con un sistema de autenticación con 3 tipos de roles.

Para ello vamos a crear una nueva migración para añadir un campo rol a los usuarios y que sea de tipo entero:

rails g migration addRoleToUsers role:integer 

Revisar el archivo de migración generado y luego añadir lo siguiente para que los usuarios existentes se les asigne el rol 0 (que será equivalente a un usuario normal) y a los futuros usuarios creados:

#db/migrate/2023....._add_role_to_users.rb class AddRoleToUsers < ActiveRecord::Migration[7.0] def change add_column :users, :role, :integer, default: 0 end end

Ahora ejecutamos la migración:

rails db:migrate

De manera opcional podemos conectarnos a postgres y validarlo por medio de una query:

select email, role from users;
+-[ RECORD 1 ]--------------------+
| email | user1@gmail.com         |
| role  | 0                       |
+-[ RECORD 2 ]--------------------+
| email | user2@gmail.com         |
| role  | 0                       |
+-[ RECORD 3 ]--------------------+
| email | user3@gmail.com         |
| role  | 0                       |
+-[ RECORD 4 ]--------------------+
| email | user4@gmail.com         |
| role  | 0                       |
+-[ RECORD 5 ]--------------------+
| email | user5@gmail.com |
| role  | 0                       |
+-------+-------------------------+

Ahora en nuestro modelo User, necesitamos definir la enumeración de la siguiente forma:

class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable enum :role, [:usuario,:conserje, :admin] end

El edificio debe contar con la información de en qué ciudad está ubicado. Para esto necesitamos crear otro modelo de la siguiente manera:

rails g model City name

Y debemos relacionarlo con edificio como se ve a continuación:

had

CITY

id

integer

PK

name

string

BUILDING

id

integer

PK

name

string

address

string

city_id

int

FK

Un edificio debe estar en una ciudad

Para añadir la clave foránea mediante la migración de Rails, lo hacemos con:

rails g migration addCityIdToBuildings city:references

Lo que nos daría como resultado un nuevo archivo de migración con el siguiente contenido:

# db/migrate/2023...add_city_id_to_buildings.rb class AddCityIdToBuildings < ActiveRecord::Migration[7.0] def change add_reference :buildings, :city, null: false, foreign_key: true end end

Y ejecutamos la migración con rails db:migrate.

El edificio debe contar con al menos 10 ciudades, las cuales deben cargarse por medio de un archivo CSV al ejecutar el seed:

El contenido del archivo seeds

# db/seeds.rb
require 'csv'    

csv_text = File.read('db/ciudades.csv')
csv = CSV.parse(csv_text, :headers => true, :encoding => 'utf-8')
csv.each do |i|
  City.create(name: i)
end

El contenido del archivo ciudades.csv:

name
arica
santiago
concepción
iquique
la serena
osorno
antofagasta
valparaiso
taltal
temuco

Tambien nos pides saber si un departamento se encuentra o no disponible. Esto se debe lograr utilizando una columna estado. Para ello agregamos un nuevo campo al modelo Apartment por medio de la migración:

rails g migration AddStatusToApartments status:integer

Y no nos olvidemos de agregar en el archivo de migración el valor por defecto para los registros existentes:

# db/migrate/2023..._add_status_to_apartments class AddStatusToApartments < ActiveRecord::Migration[7.0] def change add_column :apartments, :status, :integer, default: 0 end end

Y ahora en modelo de Apartment definir el status por medio de un enum:

# app/models/apartment.rb class Apartment < ApplicationRecord belongs_to :building enum :status, ['no disponible', 'disponible'] end

Accesos

Usuarios

Pueden ver las páginas de index y show de los edificios

Conserjes

El conserje puede acceder tanto a edificios como departamentos y solamente editar la información de los modelos (building y apartment).

Admin

El admin puede realizar todas las acciones del crud en ambos modelos (building y apartments).


Creando los controladores

CRUD PARA BUILDING

En el index de building, se listarán todos los edificios en una tabla con sus datos respectivos

Para building vamos a tener el siguiente controlador:

rails d controller buildings index new show

Configurar las rutas de building usando resources:

Rails.application.routes.draw do
  devise_for :users
  resources :buildings
  root "home#index"
end

CRUD PARA APARTMENT

def index
  @apartments = Apartment.all
end

Crear las vistas y formularios de los modelos