DEV Community

Kiana
Kiana

Posted on

Ruby on Rails, MVC architecture & data modeling

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Within the list model I had to specify

"accepts_nested_attributes_for :tasks"
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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'

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

within the views I used link_to helpers again.

<h4><%= link_to "edit", edit_task_path %></h4>
Enter fullscreen mode Exit fullscreen mode

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' %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

and placed

<%= render 'errors' %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)