For my 3rd project with Flatiron I was tasked with building a complete Ruby on Rails application that manages related data through complex forms and RESTful routes.
This was a real opportunity to put together all my knowledge about Ruby on rails, MVC architecture and data modeling.
The idea was to build a content management system.
After some brainstorming I decided to build a Task manager app.
This would serve as a place to start with my table associations in an easy way. A lists would belong to a user and a list would have many tasks.
I started with a new rails app and began on the backend.
I started with my tables for and everything I assumed a task would need.
create_table "lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "task"
end
create_table "tasks", force: :cascade do |t|
t.string "task"
t.boolean "completed"
t.date "due_date"
t.text "details"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
From there I set up my controllers and basic model associations.
class List < ActiveRecord::Base
has_many :tasks, dependent: :destroy
accepts_nested_attributes_for :tasks
end
class Task < ActiveRecord::Base
belongs_to :list
end
class User < ApplicationRecord
end
Within the list model I had to specify
"accepts_nested_attributes_for :tasks"
this would be how rails would know how to find the nested attributes for a list.
I then installed the Devise gem to add user authentication.
I have used the devise gem before and I love how straight forward the implementaion is.
It does all the heavy lifting of setting up sign up/log in functionalities along with validations.
I followed the rails docs to add it,
"https://guides.railsgirls.com/devise"
Moving onto the (V)iews part of my MVC pattern I set up the corresponding folders.
list
-index.html.erb
-show.html.erb
task
-index.html.erb
-show.html.erb
I begin with creating some dummy data in my seed.rb, This would allow me to set up functionalities on the browser and display them how I liked.
Task.create(task: "Roadtrip", details: "Rent car")
Task.create(task: "Wedding", details: "In mexico")
Task.create(task: "Eye appt", details: "Mon at 11am")
List.create(name: "Trips")
List.create(name: "Appointments")
User.create(email: "kianajade42@gmail.com", password: "password")
User.create(email: "kjl@gmail.com", password: "pass")
On the list/index.html.erb I set up some basic displaying of the data along with a link_to helper that would take a user to a partial form to create a list that accepted nested attributes for tasks.
<h3 class='title'> Currently To Do:</h3>
<h3><%= link_to "Add New", new_list_path %></h3>
<% @lists.each do |list| %>
<h4 class="list-item"> <%=list.name%> </h4>
<% end %>
Being new at routing a rails I ran rails routes in my console which provided me with the correct link to the new/create methods in my lists_controller.
I had to specify in my routes.rb the exact route.
routes.rb
post '/lists/:id/tasks/new', to: 'tasks#create'
This is telling rails that when the specified /new route is called to look for the new/create methods in the controller and post it to the database.
def new
@user = User.find_by(@current_user)
@list = List.new
@list.tasks.build
end
def create
@list = List.new(list_params)
@list.task = @current_user
if @list.save
redirect_to @list
else
render :new
end
end
The new is responsible for rendering the form and the create is what is responsible for creating/storing the data.
I created a partial form within my list folder.
to display this logic.
_list.html.erb
<%= form_for(@list) do |list_form| %>
<%# %= list_form.hidden_field :list_id %>
<ul> <h4>List Name: <%= list_form.text_field :name, placeholder: "Create a List" %></h4>
<%= list_form.fields_for :tasks, @list.tasks do |task_field| %>
<h4> Task: <%= task_field.text_field :task, placeholder: "Create a Task" %></h4>
<h4> Details: <%= task_field.text_field :details, placeholder: "Details" %></h4>
<h4> Confirm user id: <%= task_field.text_field :user_id, placeholder: @user.email %>
<% end %>
<h4><%= list_form.submit %></h4>
<% end %>
</ul>
</ul>
From there I created the edit/adding a new single task to a list with the same pattern.
routes.rb
get 'tasks/:id/edit', to: 'tasks#edit', as: :edit_task
patch 'tasks/:id', to: 'tasks#update'
the 'as: edit_task' appended to the route is a helper to create custom route to be called on in a link_to.
lists_controller.rb
def edit
@list = List.find_by(id: params[:id])
end
def update
@list = List.find(params[:id])
@list.update(list_params)
redirect_to list_path(@list)
end
tasks_controller.rb
def edit
@task = Task.find(params[:id])
end
def update
@task = Task.find(params[:id])
@task.update(task_params)
redirect_to list_tasks_path(@task)
end
within the views I used link_to helpers again.
<h4><%= link_to "edit", edit_task_path %></h4>
To practice a DRY code, because my new/edit forms were working with the same fields I called upon the form in both folders.
lists/new.html.erb
<h4><%= link_to "view all lists", lists_path %></h4>
<ul><h2>Create New</h2>
<%= render 'list' %>
lists/edit.html.erb
<h2>Editing:</h2>
<h3><%= @list.name %></h3>
<%= render 'list' %>
Another requirement of this project was to creating validations on my form, this was an easy add.
Within the controllers/models I specified what needed to be in the params and what to validate.
list.rb
validates_presence_of :name, uniqueness: true
task.rb
validates_presence_of :task, :details, presence: true
list_controller.rb
def list_params
params.require(:list).permit(:name, :task, tasks_attributes: [:task, :completed, :due_date, :details])
end
tasks_controller.rb
def task_params
params.require(:task).permit(:task, :completed, :due_date, :details)
end
From there I created an _errors file that could be used within the form.
lists/_errors.html.erb
<% if @list.errors.any? %>
<div>
<h2>
<%= pluralize(@list.errors.count, "error") %>
prohibited this from being saved:
</h2>
<ul>
<% @list.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
and placed
<%= render 'errors' %>
at the top of my _list.html.erb form.
This would render an error message if a user tried to create a list without a name.
I followed this pattern again within the tasks folder and replaced the necessary instance variables.
I had an idea that I wanted a user to be able to see the most recently created tasks with a little quick view feature on the homepage. This was mostly done with CSS and I enjoyed figuring out how to use it to my advantage to create a unique feature.
At the end of the day it was a pretty basic stacking of div's that I displayed different data on using a scope method within my task model.
I set a hidden hover effect on the top div which would would then show the recently created tasks.
I felt this feature really made it possible to show the MVC pattern in a simple way.
class Task < ActiveRecord::Base
scope :most_recent, -> {order(created_at: :desc).limit(2)}
end
This was a most_recent method that can be used in the controller telling it to show the tasks displayed by the most recent in descending order(last two created) but only the last 2.
lists_controller.rb
def index
@list = Task.most_recent
end
lists/index.html.erb
<div class=out>
<h2> Recently added </h2>
<ul>
<div class=quick>
<% @list.each do |l| %>
<div class= "list-item">
<h4> <%= l.task %> </h4>
</div>
<% end %>
</div>
</ul>
<div class="innerquick">
<h3> Quick view </h3>
</div>
</div>
For sake of space only the relevant css is below.
application.sccs
.out {
position: relative;
margin-top: 2px;
margin-right: 150px;
margin-left: 150px;
padding: 15px;
height: 300px;
}
.quick {
display: grid;
grid-template: 1fr / 1fr;
font-size: 1.5em;
padding: 5px;
text-align: center;
position: absolute;
width: fit-content;
block-size: fit-content;
margin-left: 20px;
line-height: 200px;
}
.innerquick {
display: grid;
font-size: 1.5em;
padding: 75px;
text-align: center;
position: absolute;
width: fit-content;
block-size: fit-content;
line-height: 500px;
}
.innerquick:hover {
opacity: 0;
transition-delay: 0.3s;
}
As a final requirement I has to create a join table for a has_many through association which includes a user submittable attribute other than its foreign keys.
I thought of expanding the app past a single user creating their own list and tasks and make it so an admin could assign certain tasks to a user and the could see what tasks were theirs to complete. I updated my model associations.
class List < ActiveRecord::Base
has_many :tasks, dependent: :destroy
has_many :users, through: :tasks
end
class Task < ActiveRecord::Base
belongs_to :list
belongs_to :user
end
class User < ApplicationRecord
has_many :tasks
has_many :lists, through: :tasks
end
and adding a new users folder along with index and show pages so i could display all the users and their associated tasks.
users/index.html.erb
<h1> All Users: </h1>
<div>
<ul class="tasks-list">
<% @user.each do |t| %>
<li class="list-item">
<h3><%= link_to t.email, show_path(t)%></h3>
</li>
<%end%>
</ul>
</div>
users/show.html.erb
<h2> All tasks for </h2>
<h1><%= @user.email%>: </h1>
<div>
<div>
<ul class="tasks-list">
<div class="list-item">
<% if !@user.tasks.nil? %>
<% @user.tasks.each do |t| %>
<h3><%=t.task %></h3>
<h4><%= t.details %></h4>
<%end%>
<%else%>
<h3> This user has no tasks to complete</h3>
<%end%>
</div>
</ul>
</div>
</div>
setting up the has_many through: allowed me to chain logic onto instances of the User model that now would respond to a 'tasks' method.
I also was able to use this within my tasks index and show page to display the same association.
tasks/show.html.erb
<h3 class="tasks-header"> <%= @task.task %></h3>
<div class="tasks-item">
<div>
<h4>Details:</h4>
<p><%= @task.details %></p>
</div>
</div>
<h5>this task belongs to:
<%=@task.user.email %> </h5>
<%= button_to "Delete", tasks_path(@task), :method => :delete %>
tasks/index.html.erb
<ul class="tasks-list">
<% @tasks.each do |t| %>
<li class="list-item">
<h3><%=t.task %></h3>
<div>
<h4><%= t.details %></h4>
</div>
<h5>belongs to <%=t.user.email%> </h5>
</li>
<%end%>
</ul>
In my index page I had to remember I was mapping over the data and therefore had to append t.user.email instead of defining the instance of task itself.
I had plenty of other features in this project that generally fit the same pattern. Thats what I find so great about Ruby on Rails, MVC architecture & data modeling, it provides an opportunity for repetition. There is no better way than building out a project and the executing all details that allow these concepts to fully be grasped.
On to the next project!
Top comments (0)