Let's build for Ruby and Rails developers - Part 4

justalever

Andy Leverenz

Posted on August 7, 2020

Let's build for Ruby and Rails developers - Part 4

Creating the core data models

Welcome to part 4 of my Let's build for Ruby and Rails developers series. This part will focus on generating our core model layer inside the Ruby on Rails application.

Find the code for this session here or by clicking the button in the sidebar to the right.

Data modeling

The core models we'll leverage in this app, to begin with, will be the Job and User model. In a previous part, I mentioned taking a simpler route with the User model that prefers boolean attributes as to a fully separated persona of user models.

Our User model ends up with a few sets of boolean columns including:

  • developer
  • admin
  • employer

We got there by generating a new migration:

rails g migration add_personas_to_users developer:boolean employer:boolean

Enter fullscreen mode Exit fullscreen mode

Inside the migration file I added a default value to false for each column:

# db/migrate/XXXXXXXXXXXXX_add_personas_to_users.rb
class AddPersonasToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :developer, :boolean, default: false
    add_column :users, :employer, :boolean, default: false
  end
end

Enter fullscreen mode Exit fullscreen mode

A given User can be any combination of these. Coming up I'll create some helpful methods that make checking against these constraints easier to handle. We'll leverage these throughout the app.

Updating Devise

Because we are updating our User model we need to permit additional parameters on via the Devise gem. This takes place in our application_controller.rb file and looks like this:

# app/controllers/application_controller.rb
...
def configure_permitted_parameters
  keys = [:name, :developer, :employer]
  devise_parameter_sanitizer.permit(:sign_up, keys: keys)
  devise_parameter_sanitizer.permit(:account_update, keys: keys)
end

Enter fullscreen mode Exit fullscreen mode

With this intact, we can now pass these values from the front-end during user sign up and update actions.

Our views need some updates to include these new fields as well

<!-- app/views/devise/registration/edit.html.erb -->
<h3 class="mb-4 text-lg font-bold">Roles</h3>

<div class="input-group">
  <%= f.check_box :developer %>
  <%= f.label :developer, "Use railsdevs.com as a developer?" %>
</div>

<div class="input-group">
  <%= f.check_box :employer %>
  <%= f.label :employer, "Use railsdevs.com as an employer?" %>
</div>


<!-- app/views/devise/registration/new.html.erb -->
<h3 class="mb-4 text-lg font-bold">Roles</h3>

<div class="input-group">
  <%= f.check_box :developer %>
  <%= f.label :developer, "Use railsdevs.com as a developer?" %>
</div>

<div class="input-group">
  <%= f.check_box :employer %>
  <%= f.label :employer, "Use railsdevs.com as an employer?" %>
</div>

Enter fullscreen mode Exit fullscreen mode

The Job model

Our other core model is of course the Job model. The main focus of this app is a job board for ruby and rails developers. We'll be paying the most attention to this model by design.

To save a lot of time I generated a scaffold for the Job resource.

Initially, we'll add some columns I know we need but later on, this might need to be tweaked. We can do that with additional migrations or go back in time using rails db:rollback to tweak the initial migration. We'll cross that bridge once we get to it.

rails g scaffold Job company_name company_website compensation_range link_to_apply remote:boolean role_type title years_of_experience user:references

Enter fullscreen mode Exit fullscreen mode

There's a lot going on in the code above but at its core, we'll be generating a full RESTful construct for the Job model. This effectively creates a new database table called jobs, and all the files relative to the MVC concept in a Ruby on Rails application. We'll get some extra files we don't need but that's okay for now. We can always do some housekeeping later.

Note the names I passed after Job in the string above. Each of these will generate a new column on the jobs database table. For each name, you can pass additional options prefaced by a : sign. Note the remote:boolean option. This tells ActiveRecord (the DSL language and library that talks to the database for us) that this column should be a boolean type.

If you don't pass the option at all it's assumed a type of String in the database.

Running this generator should create a full resource for Job as well as set a reference to our User model thanks to the user:references option. This option is a huge time saver that automatically generates a user_id column on the jobs database table. From there, we can assign a given user a job once they create a new one which effectively allows them to "own" that specific job. This will be useful coming up when we want to give users the ability to create, edit, update, and delete a job on their own.

Once the generation is complete you may notice some updates to the app/models/job.rb file after creation. There should be a new belongs_to :user association already in place.

We still need to add an additional association to the app/models/user.rb file but that's a quick and easy task:

# app/models/user.rb
class User < ApplicationRecord
  include SimpleDiscussion::ForumUser
  has_person_name
  has_many :jobs, dependent: :destroy # add this line
  ...
end

Enter fullscreen mode Exit fullscreen mode

Because a Job belongs to a User, we want a user to be able to "own" and manage multiple jobs. The line above allows just that.

The dependent: :destroy option means that if a User account is deleted for whatever reason, all the associated jobs will be deleted as well. This is a good practice to keep only the necessary data in your database at all times. You don't always need this option(especially if you want to retain that data) but I think it's a good practice for the most part.

A few view updates

Aside from those major updates, I took a brief moment to update our main views

<!-- app/views/shared/_head.html.erb -->
    <% if content_for?(:title) %>
      <%= yield :title %> |
    <% end %>
    railsdevs.com
  </title>

  <%= csrf_meta_tags %>
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  <%= javascript_include_tag 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %>
</head>

Enter fullscreen mode Exit fullscreen mode

Our _head.html.erb partial now loads the Stripe.js library we'll need later on and we updated the site title to railsdevs.com.

Inside our app/views/shared/_header.html.erb file I added a quick access link to our new job resource. We'll change this later on but for now, this makes it easier to navigate to.

<!-- around line 11 -->
<div class="items-center block w-full text-center lg:flex-1 lg:flex lg:text-left">
      <div class="lg:flex-grow">
        <%= link_to "Basic Link", "#", class: "block mt-4 lg:inline-block lg:mt-0 lg:mr-4 mb-2 lg:mb-0 link" %>
        <%= link_to "Jobs", jobs_path, class: "block mt-4 lg:inline-block lg:mt-0 lg:mr-4 mb-2 lg:mb-0 link" %>
      </div>
      <div class="items-center block w-full mt-2 text-center lg:flex lg:flex-row lg:flex-1 lg:mt-0 lg:text-left lg:justify-end">
        <% if user_signed_in? %>

Enter fullscreen mode Exit fullscreen mode

Install action text and active storage

Finally, we installed both action text and active storage in one command:

rails action_text:install

Enter fullscreen mode Exit fullscreen mode

This should fetch the appropriate JavaScript and Ruby dependencies we'll leverage later on.

The series so far

πŸ’– πŸ’ͺ πŸ™… 🚩
justalever
Andy Leverenz

Posted on August 7, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related