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

Encyclify - Backend Eng Assessment #305

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 74 additions & 0 deletions app/controllers/articles_controller.rb
@@ -0,0 +1,74 @@
class ArticlesController < ApplicationController
before_action :set_article, only: %i[ show edit update destroy ]

# GET /articles or /articles.json
def index
if params[:query].present?
@articles = Article.search(params[:query])
else
@articles = Article.all
end
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
22 changes: 22 additions & 0 deletions app/models/article.rb
@@ -0,0 +1,22 @@
class Article < ApplicationRecord
# title and content must be present & unique
validates_presence_of :title, :content
validates_uniqueness_of :title, :content, case_sensitive: false, message: 'must be unique'
# Set the date to today's date if it's not specified
after_initialize :set_default_date

# Search for articles where the title or content contain a specified query.
#
# @param query [String] The search term to be used in the query.
# @return [ActiveRecord::Relation] A scope containing the filtered records.
def self.search(query)
sanitized_query = sanitize_sql_like(query)
where("title LIKE ? OR content LIKE ?", "%#{sanitized_query}%", "%#{sanitized_query}%")
end

private

def set_default_date
self.date ||= Date.today
end
end
22 changes: 22 additions & 0 deletions app/views/articles/_article.html.erb
@@ -0,0 +1,22 @@
<div id="<%= dom_id article %>">
<p>
<strong>Title:</strong>
<%= article.title %>
</p>

<p>
<strong>Content:</strong>
<%= article.content %>
</p>

<p>
<strong>Author:</strong>
<%= article.author %>
</p>

<p>
<strong>Date:</strong>
<%= article.date %>
</p>

</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.date_field :date %>
</div>

<div>
<%= form.submit %>
</div>
<% end %>
4 changes: 4 additions & 0 deletions app/views/articles/_search.html.erb
@@ -0,0 +1,4 @@
<%= form_with(url: articles_path, method: :get) do |form| %>
<%= form.text_field :query, placeholder: "Find articles", value: params[:query] %>
<%= form.submit "Search" %>
<% 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>
16 changes: 16 additions & 0 deletions app/views/articles/index.html.erb
@@ -0,0 +1,16 @@
<p style="color: green"><%= notice %></p>

<h1>Articles</h1>

<%= render "search" %>

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

<%= link_to "New article", new_article_path %>
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
3 changes: 2 additions & 1 deletion 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

# Defines the root path route ("/")
# root "posts#index"
root "articles#index"
end
12 changes: 12 additions & 0 deletions db/migrate/20240129193739_create_articles.rb
@@ -0,0 +1,12 @@
class CreateArticles < ActiveRecord::Migration[7.1]
def change
create_table :articles do |t|
t.string :title
t.text :content
t.string :author
t.date :date

t.timestamps
end
end
end
23 changes: 23 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions test/controllers/articles_controller_test.rb
@@ -0,0 +1,59 @@
require "test_helper"

class ArticlesControllerTest < ActionDispatch::IntegrationTest
setup do
Article.destroy_all
@article = Article.create!(author: "Author", content: "Content", date: Date.today, title: "Title")
end

test "should get index" do
get articles_url
assert_response :success
end

test "should get new" do
get new_article_url
assert_response :success
end

test "should not create article with duplicate title" do
assert_no_difference("Article.count") do
post articles_url, params: { article: { author: @article.author, title: @article.title , content: "blah"} }
end
end

test "should not create article with duplicate content" do
assert_no_difference("Article.count") do
post articles_url, params: { article: { author: @article.author, title: "blah" , content: @article.content} }
end
end

test "should create new article" do
assert_difference("Article.count") do
post articles_url, params: { article: {title: "blah" , content: "blah"} }
end
assert_redirected_to article_url(Article.last)
end

test "should show article" do
get article_url(@article)
assert_response :success
end

test "should get edit" do
get edit_article_url(@article)
assert_response :success
end

test "should update article" do
patch article_url(@article), params: { article: { author: @article.author, content: @article.content, date: @article.date, title: @article.title } }
assert_redirected_to article_url(@article)
end

test "should destroy article" do
assert_difference("Article.count", -1) do
delete article_url(@article)
end
assert_redirected_to articles_url
end
end
1 change: 1 addition & 0 deletions test/fixtures/articles.yml
@@ -0,0 +1 @@
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
36 changes: 36 additions & 0 deletions test/models/article_test.rb
Expand Up @@ -2,13 +2,40 @@

class ArticleTest < ActiveSupport::TestCase
test 'starts with no articles' do
Article.delete_all
assert_equal 0, Article.count
end

test 'has search functionality' do
assert_respond_to Article, :search
end

test "can't create article without title" do
article = Article.create(content: 'blah')
assert_not article.valid?
assert_includes article.errors[:title], "can't be blank"
end

test "can't create article without content" do
article = Article.create(title: 'blah')
assert_not article.valid?
assert_includes article.errors[:content], "can't be blank"
end

test "should not create article with duplicate title" do
article1 = Article.create(title: 'Sample Article', content: 'c1')
article2 = Article.new(title: 'Sample Article', content: 'c2')
assert_not article2.valid?
assert_includes article2.errors[:title], 'must be unique'
end

test "should not create article with duplicate content" do
article1 = Article.create(title: 'Sample Article 1', content: 'c')
article2 = Article.new(title: 'Sample Article 2', content: 'c')
assert_not article2.valid?
assert_includes article2.errors[:content], 'must be unique'
end

test 'creates a new article' do
article = Article.create(title: 'Sample Article', content: 'Lorem ipsum dolor sit amet.')
assert article.valid?
Expand Down Expand Up @@ -65,4 +92,13 @@ class ArticleTest < ActiveSupport::TestCase
assert_includes results, article2
assert_not_includes results, article1
end

test 'returns search results for query in between' do
article1 = Article.create(title: 'Sample Article', content: 'Lorem ipsum dolor sit amet.')
article2 = Article.create(title: 'Another Article', content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.')
results = Article.search('consectetur')
assert_not_includes results, article1
assert_includes results, article2
end

end