I Built a Sinatra App for Landlords to list and manage their properties.
For our second FlatIron project, we were tasked to build a CRUD, MVC app using Sinatra. It should be a custom app that we create to track something that is important to us.
I decided to build an app for Landlords because I have been a tenant for the past 4 years and have had close relationships with my Landlords. I was always interested in what they did so I decided to make an app that would help landlords manage their properties.
Pre-requisites
My application utilizes these basic concepts:
Basic Ruby concepts
Object-oriented programming in Ruby
CRUD: create, read, update, and delete. Four basic functions of persistent storage.
MVC: model-view-controller software design pattern.
Basic HTML and CSS
Planning
My first step to planning out this project was building a checklist from my Roam database.
For my first project, I used a kanban board on Notion. For this project, I decided to use a basic checklist. After utilizing both, I think I prefer using the kanban board as a project manager. It's a lot more visual which I really like.
Before writing down any code I created my association declaration and a visual representation of my models.
Association declarations enable models in your program to have relationships.
class User < ActiveRecord
has_many :properties
has_many :tenants, through: :properties
end
class Property < ActiveRecord
belongs_to :user
has_one :tenant
end
class Tenant < Active Record
belongs_to :property
end
In this declaration, we can see that User/Landlord "has many" properties and "has many" tenants through its properties. The Property class "belongs to" a landlord and "has one" tenant. The Tenant class "belongs to" to a property.
To better describe my representations, I create a visual model.
With the Visual model, it's much simpler to see how these relationships are represented.
Allocating Responsibilities
In an Object-Oriented application, it's important to distribute the tasks to different models and controllers. Based on my observation, I created these three models:
- A User class, responsible for creating each instance of a user/landlord. It's also responsible for creating a relationship with the property class.
class User < ActiveRecord::Base
has_secure_password
has_many :properties
has_many :tenants, through: :properties
end
- A Property Class, responsible for creating each instance of a property. It's also responsible for knowing that it belongs to a landlord and that it has one tenant.
class Property < ActiveRecord::Base
belongs_to :user
has_one :tenant
end
- A Tenant class, responsible for creating each instance of a tenant. It's also responsible for knowing it belongs to a property.
class Tenant < ActiveRecord::Base
belongs_to :property
end
The controller class inherits from the Application Controller and has methods just like any other class. The controller differs in a regular class in that it responds to requests from the router.
So in all the controller will receive a request, fetch or save data from a model, and use a view to create HTML output. The controller can be looked at as the middleman between the model and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates user data to the model.
- Application controller, responsible for rendering the welcome view, holds the helper methods which all the other controllers can use.
require './config/environment'
class ApplicationController < Sinatra::Base
configure do
set :public_folder, 'public'
set :views, 'app/views'
enable :sessions
set :session_secret, "password_security"
end
get "/" do
@logged_in = logged_in?
erb :welcome
end
helpers do
def logged_in?
!!session[:user_id]
end
def current_user
User.find(session[:user_id])
end
end
end
- User controller, responsible for rendering users home page, the signup form, login form, and logout requests.
class UsersController < ApplicationController
get '/users/home' do
if logged_in?
erb :"users/index"
else
redirect '/login'
end
end
get '/signup' do
if logged_in?
redirect '/properties'
end
erb :"users/create_user"
end
post '/signup' do
unless params[:name] == "" || params[:email] == ""
user = User.new(name: params[:name], email: params[:email], password: params[:password])
if user.save
session[:user_id] = user.id
redirect '/properties'
end
end
redirect '/signup'
end
get '/login' do
unless logged_in?
erb :'users/login'
else
redirect '/users/home'
end
end
post '/login' do
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect '/users/home'
else
redirect '/login'
end
end
get '/logout' do
if logged_in?
session.clear
end
redirect '/login'
end
post '/logout' do
session.clear
redirect '/login'
end
end
- Property controller, responsible for rendering the users properties, new properties form, processing the new property form, rendering a specific property, updating a specific property, and deleting a specific property.
class PropertiesController < ApplicationController
get '/properties' do
if logged_in?
@first_name = current_user.name.split[0]
@properties = current_user.properties
erb :"/properties/index"
else
redirect '/login'
end
end
get '/properties/new' do
if logged_in?
@tenants = Tenant.all.select { |x| x.property_id == nil}
erb :"/properties/new"
else
redirect '/login'
end
end
post '/properties' do
@property = current_user.properties.build(address: params[:property][:address], description: params[:property][:description], rent: params[:property][:rent])
if !@property.address.empty? && !@property.description.empty? && !@property.rent.empty?
@property.save
if params[:tenant]
@property.tenant = Tenant.find_by_id(params[:tenant][:tenant_id][0].to_i)
end
redirect '/properties'
else
redirect '/properties/new'
end
end
get '/properties/:id' do
if logged_in?
@property = current_user.properties.find_by_id(params[:id])
erb :"properties/show"
else
redirect '/login'
end
end
get '/properties/:id/edit' do
if logged_in?
@property = current_user.properties.find_by_id(params[:id])
@tenants = Tenant.all.select { |x| x.property_id == nil}
erb :"properties/edit"
else
redirect
end
end
patch '/properties/:id' do
@property = current_user.properties.find_by_id(params[:id])
@property.update(params[:property])
if params[:tenant]
@property.tenant = Tenant.find_by_id(params[:tenant][:tenant_id][0].to_i)
redirect "/properties/#{@property.id}"
else
redirect "/properties/#{@property.id}"
end
end
delete '/properties/:id' do
@property = current_user.properties.find_by_id(params[:id])
@property.tenant = nil
@property.destroy
redirect '/properties'
end
end
- Tenant controller didn't have any responsibilities because I wanted to simulate that there are already tenants on the market so I created some seed data.
class TenantsController < ApplicationController
end
Conclusion
This project was honestly a lot of fun to build. I really felt like FlatIron prepared us enough to do well on this project. There were times where I felt stuck or lost but using methods such as binding.pry helped a lot.
Fork/clone the project from my repo and give it a go!
Happy coding!