Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Camran Hansen Shopify Eng Assessment #306

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
40 changes: 23 additions & 17 deletions Gemfile
@@ -1,30 +1,30 @@
source "https://rubygems.org"
source 'https://rubygems.org'

ruby "2.7.6"
ruby '2.7.6'

# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.2"
gem 'rails', '~> 7.1.2'

# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
gem 'sprockets-rails'

# Use sqlite3 as the database for Active Record
gem "sqlite3", "~> 1.4"
gem 'sqlite3', '~> 1.4'

# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
gem 'puma', '>= 5.0'

# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
gem "importmap-rails"
gem 'importmap-rails'

# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
gem 'turbo-rails'

# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
gem 'stimulus-rails'

# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
gem 'jbuilder'

# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"
Expand All @@ -36,22 +36,28 @@ gem "jbuilder"
# gem "bcrypt", "~> 3.1.7"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ mswin mswin64 mingw x64_mingw jruby ]
gem 'tzinfo-data', platforms: %i[mswin mswin64 mingw x64_mingw jruby]

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
gem 'bootsnap', require: false

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

# ⚡ A Scope & Engine based, clean, powerful, customizable and sophisticated paginator for Ruby webapps [https://github.com/kaminari/kaminari]
gem 'kaminari'

# bootstrap 4
gem 'bootstrap', '~> 4.0'

group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri mswin mswin64 mingw x64_mingw ]
gem 'debug', platforms: %i[mri mswin mswin64 mingw x64_mingw]
end

group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
gem 'web-console'

# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"
Expand All @@ -62,7 +68,7 @@ end

group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
gem 'capybara'
gem 'selenium-webdriver'
gem 'webdrivers'
end
32 changes: 32 additions & 0 deletions Gemfile.lock
Expand Up @@ -77,11 +77,17 @@ GEM
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
autoprefixer-rails (10.4.16.0)
execjs (~> 2)
base64 (0.2.0)
bigdecimal (3.1.5)
bindex (0.8.1)
bootsnap (1.17.0)
msgpack (~> 1.2)
bootstrap (4.6.2)
autoprefixer-rails (>= 9.1.0)
popper_js (>= 1.16.1, < 2)
sassc-rails (>= 2.0.0)
builder (3.2.4)
capybara (3.39.2)
addressable
Expand All @@ -102,6 +108,8 @@ GEM
drb (2.2.0)
ruby2_keywords
erubi (1.12.0)
execjs (2.9.1)
ffi (1.16.3)
globalid (1.2.1)
activesupport (>= 6.1)
i18n (1.14.1)
Expand All @@ -117,6 +125,18 @@ GEM
jbuilder (2.11.5)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
loofah (2.22.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
Expand Down Expand Up @@ -149,6 +169,7 @@ GEM
racc (~> 1.4)
nokogiri (1.15.5-x86_64-linux)
racc (~> 1.4)
popper_js (1.16.1)
psych (5.1.1.1)
stringio
public_suffix (5.0.4)
Expand Down Expand Up @@ -201,6 +222,14 @@ GEM
rexml (3.2.6)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sassc (2.4.0)
ffi (~> 1.9)
sassc-rails (2.1.2)
railties (>= 4.0.0)
sassc (>= 2.0)
sprockets (> 3.0)
sprockets-rails
tilt
selenium-webdriver (4.9.0)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
Expand All @@ -220,6 +249,7 @@ GEM
railties (>= 6.0.0)
stringio (3.1.0)
thor (1.3.0)
tilt (2.3.0)
timeout (0.4.1)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
Expand Down Expand Up @@ -252,10 +282,12 @@ PLATFORMS

DEPENDENCIES
bootsnap
bootstrap (~> 4.0)
capybara
debug
importmap-rails
jbuilder
kaminari
puma (>= 5.0)
rails (~> 7.1.2)
selenium-webdriver
Expand Down
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css
Expand Up @@ -13,3 +13,4 @@
*= require_tree .
*= require_self
*/
@import "bootstrap";
72 changes: 72 additions & 0 deletions app/controllers/articles_controller.rb
@@ -0,0 +1,72 @@
class ArticlesController < ApplicationController
before_action :set_article, only: %i[show edit update destroy]

# GET /articles or /articles.json
def index
# display articles relevant to search term
# note: if search term is empty, all articles are returned.
# sort articles by date, and paginate at 5 per page
@articles = Article.search(params[:term]).order(:date).page(params[:page]).per(5)
end

# GET /articles/1 or /articles/1.json
def show; end

# GET /articles/new
def new
@article = Article.new
end

# GET /articles/1/edit
def edit; end

# POST /articles or /articles.json
def create
@article = Article.new(article_params)

respond_to do |format|
if @article.save
format.html { redirect_to article_url(@article), notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: @article }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end

# PATCH/PUT /articles/1 or /articles/1.json
def update
respond_to do |format|
if @article.update(article_params)
format.html { redirect_to article_url(@article), notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: @article }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end

# DELETE /articles/1 or /articles/1.json
def destroy
@article.destroy!

respond_to do |format|
format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
format.json { head :no_content }
end
end

private

# Use callbacks to share common setup or constraints between actions.
def set_article
@article = Article.find(params[:id])
end

# Only allow a list of trusted parameters through.
def article_params
params.require(:article).permit(:title, :content, :author, :date)
end
end
2 changes: 2 additions & 0 deletions app/helpers/articles_helper.rb
@@ -0,0 +1,2 @@
module ArticlesHelper
end
19 changes: 19 additions & 0 deletions app/models/article.rb
@@ -0,0 +1,19 @@
class Article < ApplicationRecord
validates :title, presence: true
validates :content, presence: true, length: { minimum: 10 }

#
# return articles where title, content, or author contains the search term
# or return all articles if the search term is blank
#
def self.search(term)
if term.blank?
all
else
# use sanitized search term for query
# see documentation below:
# https://api.rubyonrails.org/v7.1.3/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_like
where('title LIKE :term OR content LIKE :term OR author LIKE :term', term: "%#{sanitize_sql_like(term)}%")
end
end
end
8 changes: 8 additions & 0 deletions app/views/articles/_article.html.erb
@@ -0,0 +1,8 @@
<div id="<%= dom_id article %>">
<h1><%= article.title %></h1>

<h4> By: <%= article.author %> on <i> <%= article.date %> </i> </h4>

<%= article.content %>

</div>
2 changes: 2 additions & 0 deletions app/views/articles/_article.json.jbuilder
@@ -0,0 +1,2 @@
json.extract! article, :id, :title, :content, :author, :date, :created_at, :updated_at
json.url article_url(article, format: :json)
37 changes: 37 additions & 0 deletions app/views/articles/_form.html.erb
@@ -0,0 +1,37 @@
<%= form_with(model: article) do |form| %>
<% if article.errors.any? %>
<div style="color: red">
<h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>

<ul>
<% article.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>

<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>

<div>
<%= form.label :content, style: "display: block" %>
<%= form.text_area :content %>
</div>

<div>
<%= form.label :author, style: "display: block" %>
<%= form.text_field :author %>
</div>

<div>
<%= form.label :date, style: "display: block" %>
<%= form.datetime_field :date %>
</div>

<div>
<%= form.submit %>
</div>
<% end %>
10 changes: 10 additions & 0 deletions app/views/articles/edit.html.erb
@@ -0,0 +1,10 @@
<h1>Editing article</h1>

<%= render "form", article: @article %>

<br>

<div>
<%= link_to "Show this article", @article %> |
<%= link_to "Back to articles", articles_path %>
</div>
22 changes: 22 additions & 0 deletions app/views/articles/index.html.erb
@@ -0,0 +1,22 @@
<p style="color: green"><%= notice %></p>
<h1>Create an Article</h1>
<%= link_to "New article", new_article_path %>

<h1>Articles</h1>

<%= form_with url: "/articles", method: :get do |form| %>
<%= form.label :term, "Search For Article:" %>
<%= form.text_field :term %>
<%= form.submit "Search" %>
<% end %>

<div id="articles">
<% @articles.each do |article| %>
<%= render article %>
<p>
<%= link_to "Show this article", article %>
</p>
<% end %>
</div>

<%= paginate @articles %>
1 change: 1 addition & 0 deletions app/views/articles/index.json.jbuilder
@@ -0,0 +1 @@
json.array! @articles, partial: "articles/article", as: :article
9 changes: 9 additions & 0 deletions app/views/articles/new.html.erb
@@ -0,0 +1,9 @@
<h1>New article</h1>

<%= render "form", article: @article %>

<br>

<div>
<%= link_to "Back to articles", articles_path %>
</div>
10 changes: 10 additions & 0 deletions app/views/articles/show.html.erb
@@ -0,0 +1,10 @@
<p style="color: green"><%= notice %></p>

<%= render @article %>

<div>
<%= link_to "Edit this article", edit_article_path(@article) %> |
<%= link_to "Back to articles", articles_path %>

<%= button_to "Destroy this article", @article, method: :delete %>
</div>
1 change: 1 addition & 0 deletions app/views/articles/show.json.jbuilder
@@ -0,0 +1 @@
json.partial! "articles/article", article: @article
5 changes: 3 additions & 2 deletions config/routes.rb
@@ -1,10 +1,11 @@
Rails.application.routes.draw do
resources :articles
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
get 'up' => 'rails/health#show', as: :rails_health_check

# Defines the root path route ("/")
# root "posts#index"
root 'articles#index'
end