Jim's
Tutorials

Spring 2019
course
site

Classroom CMS

This week I built the basic functionality of a single classroom CMS. It uses the Ruby on Rails framework with postgreSQL as a database and ERB (Embedded Ruby Templates) for the front-end views.

Database and models

There are three tables in my database. There is a users table that includes a username, email, and role. There is an Assignments table that includes the title and description of the assignment. And there is a studentwork table that includes the work submitted and foreign keys of both a user and an assignment. There's an ERB diagram attached below to make things more clear but there are only two relationships: a one to many relationship between users and studentworks, and a one to many relationship between assignments and studentworks. My models are very simple right now, they just include the active-record associations for the one to many relationship between studentworks and assignments/users. Here's an example:

class Studentwork < ApplicationRecord
  belongs_to :assignment
  belongs_to :user
end

Authentication

I integrated github oath using the ruby gem omniauth-github mostly following the above guide. This allows the app to authenticate users via their github account. There is a user model that accepts some information gathered from the connection to oath and saves the user into the database.

class User < ActiveRecord::Base
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_initialize.tap do |user|
      user.email = auth.info.email
      user.uid = auth.uid
      user.provider = auth.provider
      user.avatar_url = auth.info.image
      user.username = auth.info.name
      user.oauth_token = auth.credentials.token
      user.role = "student"
      user.save!
    end
  end
  has_many :studentWorks
end

Routes and controllers

The below routes are how my application handles various urls. The root page is the assignments index, which conditionally renders if you are logged in. That logic can be found in app/views/assignments/index.html.erb. There are a couple defined routes to create a session with oath, re-direct if the auth fails, and delete the session. Resources :assignments is shorthand for creating 7 different routes that correspond to the expected behavior in a CRUD app. In this case it's GET assignments/ POST/new GET/new PATCH/:id/edit DELETE/:id GET/:id PUT/:id. Resources :studentworks creates a set of nested routes for student works such that each studentworks page is nested inside of an assignment/:id.

Rails.application.routes.draw do
  get "/auth/:provider/callback", to: "sessions#create"
  get 'auth/failure', to: redirect('/')
  delete 'signout', to: 'sessions#destroy', as: 'signout'
  root to: 'assignments#index'

  resources :assignments do
    resources :studentworks do
    end
  end
end

To handle these routes there's a controller for each database table. My Assignments controller looks like this:

class AssignmentsController < ApplicationController
  def index
    @assignments = Assignment.all
  end

  def new
    @assignment = Assignment.new
  end

  def show
    @assignment = Assignment.find(params[:id])
    @user = current_user
    if Studentwork.all != []
      @studentworks = Studentwork.where(assignment_id: @assignment.id)
      @work = @studentworks.where(user_id: @user.id).take
    end
  end

  def create
    @assignment = Assignment.new(assignment_params)
    @user = current_user

    if @assignment.save
      flash[:notice] = "assignment created successfully"
      redirect_to assignments_path
    else
      @form_errors = @assignment.errors.full_messages
      render :new
    end
  end

  def edit
    @assignment = Assignment.find(params[:id])
  end

  def update
    @assignment = Assignment.new(assignment_params)
    @user = current_user

    if @assignment.update(assignment_params)
      redirect_to @assignment
    else
      @form_errors = @assignment.errors.full_messages
      render :edit
    end
  end

  def destroy
    @assignment = Assignment.find(params[:id])
    @assignment.destroy
    redirect_to assignments_path
  end

  def assignment_params
    params.require(:assignment).permit(:title, :assignment)
  end
end

It handles the connection between my models and my views. In the index route it simply passes all the stored Assignments to the view. In the case of the create, update and destroy routes it handles for the incoming params and uses the models to update the database properly. In the the case of the new and edit routes it passes a new instance of the proper model to hook up with the form and pass the proper params. The bottom method is a way to only permit certain params being passed down and control the flow of data. The studentwork controller is similar, except it constantly needs to be passed which assignment the studentwork is attached to.

Views

For the views I used ERB templates. Views are where I handle for the display logic, which also includes conditional rendering based on user role. Here's an example of the assignments index page:

<% if current_user.blank? %>
  <h1>Please Sign In</h1>
  <%= link_to 'Sign In with Github', '/auth/github' %>
<% else %>
    <p>
      You are signed in as <%= current_user.username %>.  Click the button below to sign out.
    </p>

    <%= button_to "Sign Out", signout_path, method: :delete %>

  <% @assignments.each do |assignment| %>
    <h1 class="assignment-link"><%= link_to assignment.title, assignment_path(assignment.id) %></h1>
    <%= assignment.assignment %>
    <% end %>
      <% if current_user.role == "admin" %>
      <p><%= link_to "Add A New Assignment", new_assignment_path %></p>
    <% end %>
<% end %>

This page checks for your login information then displays a list of assignments if you are authenticated. If your role is admin you can add a new assignment. It includes some html tags and a lot of embedded ruby code inside of <% %> tags. The <%= tag is logic whose output will be displayed on the page.

Things to Do

attachments [paper clip]

  last modified size
TXT classroom-cms.png Tue Apr 30 2024 05:49 pm 26K