Ruby on Rails, MVC architecture & data modeling
Tags: rails, ruby
Published: 2022-09-23 04:34:27

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!