Back

Resume Database Web App

Champaign, Illinois

Background


I will always take any opportunity that I can find to make a web app. So when I found out that my fraternity did not have a web accessible resume database, I took the opportunity to make one myself.

Goals/Functionalities


  • Friendly interface
  • Persistent resume files on new deploys
  • Resumes can be downloaded
  • Only users with a code can sign up for an account
  • Accounts
    • Roles ie. Brother, Admin, Employer
      • Brothers:
        • view other resumes that are not private
        • upload own resume
        • make own resume private
      • Admin:
        • see all resumes
        • delete users
        • change role of users
      • Employer:
        • see all resumes

Technologies


Accounts


To begin, I needed to implement a complete account authentication and authorization system. To do this, I used a boilerplate rails app from github that implements basic user controller actions, sign up/in flow, and role-based authorization. I thought it would be a waste of my time to implement a complete role-based authentication and authorization app when someone has already laid the foundation of an app with that purpose. Why would I try to invent the wheel again, right?

The next step would be to customize this app so that it had the roles that I wanted as well as the code-based sign up functionality.

# app/models/user.rb

enum role: [:employer, :brother, :admin]
after_initialize :set_default_role, :if => :new_record?

def set_default_role
  self.role ||= :brother
end

Then I wanted to implement the code-based sign up so the first step was to put a code in the secrets.yml file. For development, I simply just put in a two-letter easy-to-type code. But for production, I planned on grabbing the secret code from environment variables.

# config/secrets.yml

development:
  brother_code: tt
production:
  brother_code: <%= ENV["BROTHER_CODE"] %>

After adding the code, the next step was to customize the logic for signing up. The sign up action for devise is under the “create” function in the registrations’ controller. To override the “create” function, I had to first change some routes.

# config/routes.rb

# Not sure why I need to skip the sessions controller
# but it fixed duplicate routes
devise_for :users, skip: [:registrations, :sessions]
devise_for :users, controllers: {
registrations: 'my_devise/registrations'
}

To then override the devise registration controller action for “create”, I created a folder called my_devise which would contain a registration controller that simply extends the base registration controller and has an additional if-else statement that checks the brother code in the Rails secret.yml file.

# controllers/my_devise/registrations_controller.rb

class MyDevise::RegistrationsController < Devise::RegistrationsController
  # POST /resource
   def create
     if params[:user][:code] == Rails.application.secrets.brother_code
       super
     else 
       redirect_to new_user_registration_path, :alert => "Incorrect Code"
     end
   end
end

And lastly, I had to add the brother code text field to the sign up.

<!--views/devise/registrations/new.html.erb-->
<!--Other form HTML code above-->
<div class="form-group">
  <%= f.label :code %>
  <%= f.password_field :code, class: 'form-control' %>
</div>
<!--Other form HTML code below-->

And voila! I now had the roles that I wanted and code-based sign up implemented. And with the addition of pundit and some authorization logic put into app/policies/UserPolicy.rb, I was able to implement the authorization of certain pages based on roles.

File Attachment


The next hard step was to implement file-attachments for each user. Fortunately, there is an extremely convenient ruby gem called ‘paperclip’ that helps you add a file to an already-existing model. This is easily done with a few lines of code and one migration.

First I just had to add the paperclip gem to my Gemfile and then add these two lines of code to my user model.

# app/models/user.rb

has_attached_file :document, styles: {thumbnail: ["40x40#", :png]}
validates_attachment :document, content_type: { content_type: "application/pdf" }

And with a simple form, users can now just upload a pdf to the website!

<div class="form-group">
  <label for="user_document"> <%= @user.document? ? @user.document_file_name.to_s : "Document" %> </label>
  <%= f.file_field :document, class: 'form-control' %>
</div>

Surviving New Deployments


Now it was only until I actually pushed this deployment to production did I realize that the files that are uploaded through paperclip don’t survive new deployments! This was a big issue because if the website had to be updated and everyone had already uploaded all their resumes, all the file storage would be lost and everyone would have to re-upload their resume. Thankfully, paperclip has an easy-to-do solution with their configuration to use Amazon S3 Buckets for file-storage instead of locally cached files on the web-server itself.

To do this, all I had to add was this configuration to production.rb file:

# config/environments/production.rb

# Amazon S3 Bucket config
config.paperclip_defaults = {
  storage: :s3,
  s3_credentials: {
    bucket: ENV.fetch('S3_BUCKET_NAME'),
    access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
    secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
    s3_region: ENV.fetch('AWS_REGION'),
  },
  s3_host_name: "s3-#{ENV['AWS_REGION']}.amazonaws.com"
}

Conclusion


Now aside from the specific additional routes, controller logic, and html.erb code, this concludes a fairly complete implementation of a resume database that can easily be deployed to Heroku!

Link to Github Repo

Web Apps, Vim, Computers, and Dogs.
"Everything around you that you call life was made up by people that were no smarter than you and you can change it, you can influence it, you can build your own things that other people can use. Once you learn that, you'll never be the same again." ~ Steve Jobs

Read Next

Vim Addict

Github activity