diff --git a/.ruby-version b/.ruby-version index b347b11e..15a27998 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.3 +3.3.0 diff --git a/Gemfile b/Gemfile index 7abd44d7..c4f0840f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,17 @@ source "https://rubygems.org" -ruby "3.2.3" +ruby "3.3.0" + +# Adding Bootstrap for styling +gem 'bootstrap', '5.1' + +gem 'jquery-rails' # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" gem "rails", "~> 7.1.2" # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] -gem "sprockets-rails" +gem "sprockets-rails", ">= 2.3.2" # Use sqlite3 as the database for Active Record gem "sqlite3", "~> 1.4" diff --git a/Gemfile.lock b/Gemfile.lock index ac898c77..88e6c1ac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -77,17 +77,23 @@ 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.6) bindex (0.8.1) bootsnap (1.17.1) msgpack (~> 1.2) + bootstrap (5.1.0) + autoprefixer-rails (>= 9.1.0) + popper_js (>= 2.9.3, < 3) + sassc-rails (>= 2.0.0) builder (3.2.4) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -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) @@ -117,6 +125,10 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + jquery-rails (4.6.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) @@ -153,6 +165,7 @@ GEM racc (~> 1.4) nokogiri (1.16.0-x86_64-linux) racc (~> 1.4) + popper_js (2.11.8) psych (5.1.2) stringio public_suffix (5.0.4) @@ -205,6 +218,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.10.0) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) @@ -226,6 +247,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) @@ -260,14 +282,16 @@ PLATFORMS DEPENDENCIES bootsnap + bootstrap (= 5.1) capybara debug importmap-rails jbuilder + jquery-rails puma (>= 5.0) rails (~> 7.1.2) selenium-webdriver - sprockets-rails + sprockets-rails (>= 2.3.2) sqlite3 (~> 1.4) stimulus-rails turbo-rails @@ -276,7 +300,7 @@ DEPENDENCIES webdrivers RUBY VERSION - ruby 3.2.3p157 + ruby 3.3.0p0 BUNDLED WITH 2.5.5 diff --git a/README.md b/README.md index 29c5b607..aa63533d 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,23 @@ -# Technical Instructions -1. Fork this repo to your local Github account. -2. Create a new branch to complete all your work in. -3. Test your work using the provided tests -4. Create a Pull Request against the Shopify Main branch when you're done and all tests are passing - -# Project Overview -The Rails application you will be working on is an Encyclopedia, which allows users to create, view, edit, and delete articles. The application also provides search functionality to help users find relevant articles. Be sure to implement basic CRUD actions on articles. Your task is to implement these features as well as write the code that makes the tests pass. - -# Project Goals -The main goal of this internship project is to implement the functionality required to make the existing tests pass. The provided tests cover various aspects of the application, including creating and viewing articles, editing and updating articles, deleting articles, and searching for articles. Along with completing the tests, be sure to implement all basic CRUD actions on your articles on a controller and create views to see your work in the app. - -## Your specific goals for this project are as follows: - -1. Review Existing Tests: Start by reviewing the existing tests provided in the article_test.rb file located in the test/models directory. Understand the requirements and expectations of each test. - -2. Implement Functionality: Write the code necessary to make the existing tests pass. This involves implementing the required actions and logic in the models, controllers, and views to fulfill the specified requirements. Also be sure to implement basic CRUD actions and demonstrate proper MVC principals. - -3. Ensure Code Quality: Write clean, well-structured, and maintainable code. Follow best practices and adhere to the Ruby on Rails conventions. Pay attention to code readability, modularity, and performance. - -4. Test Your Code: After implementing the functionality, run the tests to ensure that they pass successfully. Fix any failures or errors that occur and retest until all tests pass. - -5. Code Documentation: Document your code by adding comments and explanatory notes where necessary. This will help other developers understand your implementation and make future maintenance easier. - -6. Version Control: Use Git for version control. Commit your changes regularly and push them to a branch in your forked repository. - -7. Create a Pull Request: Once you have completed the project goals, create a pull request to merge your changes into the main repository. Provide a clear description of the changes made and any relevant information for the code review. - -## Getting Started -To get started with this project, follow these steps: - -1. Clone the repository to your local development environment. - -2. Install the necessary dependencies by running bundle install in the project directory. - -3. Familiarize yourself with the existing codebase, including the models, controllers, and views. - -4. Review the existing tests in the article_test.rb file and understand their purpose and functionality. - -5. Run the tests locally to ensure they are passing. - -6. Start working on the goals outlined above, making improvements to the existing tests and adding new tests as needed. - -7. Commit your changes regularly and push them to a branch in your forked repository. - -8. Once you have completed the project goals, create a pull request to merge your changes into the main repository. - -## Resources -Here are some resources that may be helpful during your internship project: - -- [Ruby on Rails Guides](https://guides.rubyonrails.org/) - Comprehensive guides on Ruby on Rails, covering various aspects of web application development. - -- [Ruby Style Guide](https://rubystyle.guide/) - A community-driven Ruby coding style guide to ensure consistent and readable code. - -- [Git Documentation](https://git-scm.com/doc) - Official documentation for Git, the version control system used in this project. +# About +- This is the technical challenge portion for the backend developer internship position at Shopify. +- This project is an Encyclopedia where users can perform the basic CRUD functionalities on the site. + +# Changes made +- In order to complete the project the following changes were made: + - Model for the Article was added + - Index, show, edit, and update views were added + - Article controller was added + - Styling was added to different components + - Ruby version was changed to 3.3.0 + +# Result +- This implementation passes all the tests that were integrated + +# Improvements +- Due to my workload from work and school, I was not able to spend much time on this project. However, if I had time I would make the following changes to the project: + - Integrate Bootstrap for styling: Even though I tried to get it working, it did not work properly + - Improve UI: As of now, the page is very plain and not user friendly therefore improving the UI will improve the user experience + - Search result display: I would use a list to display the matching article right below the search bar and allow the user to click on it + +# Notes +- Thank you so much for your time and consideration, this project was a good learning project for me to get familiar with Ruby on Rails. diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 288b9ab7..3b572056 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -13,3 +13,202 @@ *= require_tree . *= require_self */ + + .navbar { + background-color: #f8f9fa; + padding: 10px 20px; + display: flex; + justify-content: space-between; + align-items: center; + } + + .navbar-brand { + font-size: 24px; + font-weight: bold; + } + + .search-form { + display: flex; + gap: 10px; + } + + .search-input { + padding: 5px 10px; + border: 1px solid #ddd; + border-radius: 4px; + } + + .search-button { + padding: 5px 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + } + + .search-button:hover { + background-color: #0056b3; + } + + .articles-table { + width: 100%; + border-collapse: collapse; + } + + .articles-table th, .articles-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + } + + .articles-table th { + background-color: #f2f2f2; + } + + .container { + margin: 20px; + text-align: center; + } + + .no-articles-message { + margin-top: 20px; + } + +.new-article-link { + position: fixed; + bottom: 20px; + left: 0; + width: 100%; + text-align: center; + padding: 10px 0; + } + + .btn-new-article { + padding: 10px 20px; + background-color: #007bff; + color: white; + text-decoration: none; + border-radius: 4px; + } + + .btn-new-article:hover { + background-color: #0056b3; + } + + .field { + margin-bottom: 15px; + } + + .field label { + display: block; + margin-bottom: 5px; + font-weight: bold; + } + + .field input[type="text"], + .field textarea { + width: 100%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; + } + + .field textarea { + height: 100px; + } + + .error { + color: red; + font-size: 0.8em; + margin-top: 5px; + } + + .btn-submit { + padding: 10px 20px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + } + + .btn-submit:hover { + background-color: #0056b3; + } + + .btn-home { + padding: 8px 12px; + color: black; + text-decoration: none; + border-radius: 4px; + margin-left: 10px; /* Space it out from the brand name or other elements */ + } + + .btn-home:hover { + background-color: #e5e5e5; + } + + .show-container { + max-width: 800px; + margin: 0 auto; + padding: 20px; + } + + .show-title { + font-size: 2em; + margin-bottom: 20px; + } + + .show-details { + font-size: 1.1em; + color: #555; + margin-bottom: 20px; + } + + .show-content { + margin-bottom: 30px; + line-height: 1.6; + color: #333; + } + + .btn { + padding: 10px 15px; + text-decoration: none; + color: white; + border-radius: 5px; + cursor: pointer; + display: inline-block; + } + + .btn-edit { + background-color: #007bff; + margin-right: 10px; + } + + .btn-edit:hover { + background-color: #0056b3; + } + + .btn-delete { + margin-top: 1rem; + background-color: #dc3545; + } + + .btn-delete:hover { + background-color: #c82333; + } + + .show-content { + background-color: #f9f9f9; + border: 1px solid #ddd; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + padding: 20px; + margin-bottom: 30px; + border-radius: 5px; + line-height: 1.6; + } + + + + \ No newline at end of file diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb new file mode 100644 index 00000000..d0781c1a --- /dev/null +++ b/app/controllers/articles_controller.rb @@ -0,0 +1,54 @@ +class ArticlesController < ApplicationController + def index + # display all the articles or filtered data + @articles = Article.search(params[:search]) + end + + def show + # find by id + @article = Article.find(params[:id]) + end + + def new + @article = Article.new + end + + def create + @article = Article.new(article_params) + + if @article.save + redirect_to @article, notice: "Successfully created a new article" # redirecting the user to the newly create article's page + else + render :new, status: :unprocessable_entity + end + end + + def edit + @article = Article.find(params[:id]) + end + + def update + @article = Article.find(params[:id]) + + if @article.update(article_params) + redirect_to @article, notice: "Successfully updated the article" + else + render :edit, status: :unprocessable_entity + end + end + + def destroy + @article = Article.find(params[:id]) + + if @article + @article.destroy + end + + redirect_to root_path, status: :see_other + end + + private + def article_params + params.require(:article).permit(:title, :author, :content, :date) + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b4940..a15fab16 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,5 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" +import "jquery" +import "bootstrap" diff --git a/app/models/article.rb b/app/models/article.rb new file mode 100644 index 00000000..8491fa07 --- /dev/null +++ b/app/models/article.rb @@ -0,0 +1,19 @@ +class Article < ApplicationRecord + # ensure that the all the data needed for an article is present + validates :title, presence: true + validates :content, presence: true + + # meta data are optional + validates :author, presence: false + validates :date, presence: false + + # define the search functionality + def self.search(search) + if search + search = search.downcase # lower case for search for better match + where("LOWER(title) LIKE ? or LOWER(author) LIKE ? or LOWER(content) LIKE ?", "%#{search}%", "%#{search}%", "%#{search}%") + else + all + end + end +end diff --git a/app/views/articles/_form.html.erb b/app/views/articles/_form.html.erb new file mode 100644 index 00000000..f9ba0cd1 --- /dev/null +++ b/app/views/articles/_form.html.erb @@ -0,0 +1,40 @@ +<%= form_with(model: article, local: true) do |form| %> +
+
+ <%= form.label :title %> + <%= form.text_field :title %> + <% article.errors.full_messages_for(:title).each do |message| %> +
<%= message %>
+ <% end %> +
+ +
+ <%= form.label :author %> + <%= form.text_field :author %> + <% article.errors.full_messages_for(:author).each do |message| %> +
<%= message %>
+ <% end %> +
+ +
+ <%= form.label :content %> + <%= form.text_area :content %> + <% article.errors.full_messages_for(:content).each do |message| %> +
<%= message %>
+ <% end %> +
+ +
+ <%= form.label :date %>
+ <%= form.date_select :date %>
+ <% @article.errors.full_messages_for(:date).each do |message| %> +
<%= message %>
+ <% end %> +
+ +
+ <%= form.submit class: "btn-submit" %> +
+
+ <% end %> + \ No newline at end of file diff --git a/app/views/articles/edit.html.erb b/app/views/articles/edit.html.erb new file mode 100644 index 00000000..f1b7ee79 --- /dev/null +++ b/app/views/articles/edit.html.erb @@ -0,0 +1,7 @@ + + +

Edit Article

+ +<%= render 'form', article: @article %> diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb new file mode 100644 index 00000000..8828dae7 --- /dev/null +++ b/app/views/articles/index.html.erb @@ -0,0 +1,41 @@ + + +
+ <% if @articles.any? %> +
+ + + + + + + + + + <% @articles.each do |article| %> + + + + + + <% end %> + +
TitleAuthorDate
<%= link_to article.title, article_path(article) %><%= article.author %><%= article.date %>
+
+ <% else %> +
+

No articles found

+
+ <% end %> + +
+ <%= link_to "New Article", new_article_path, class: "btn-new-article" %> +
+
+ diff --git a/app/views/articles/new.html.erb b/app/views/articles/new.html.erb new file mode 100644 index 00000000..37755be5 --- /dev/null +++ b/app/views/articles/new.html.erb @@ -0,0 +1,9 @@ + + +

New Article

+ +<%= render 'form', article: @article %> + + diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb new file mode 100644 index 00000000..66d6385d --- /dev/null +++ b/app/views/articles/show.html.erb @@ -0,0 +1,29 @@ + + +
+

<%= @article.title %>

+ +
+ <% if @article.author.present? %> +

Author: <%= @article.author %>

+ <% end %> + + <% if @article.date.present? %> +

Date: <%= @article.date.strftime("%d/%m/%Y") %>

+ <% end %> +
+ +
+ <%= @article.content %> +
+ +
+ <%= link_to 'Edit', edit_article_path(@article), class: "btn btn-edit" %> + <%= form_with(url: article_path(@article), method: :delete) do %> + <%= submit_tag "Delete", data: { confirm: "Are you sure you want to delete this article?" }, class: "btn btn-delete" %> + <% end %> + +
+
diff --git a/app/views/layouts/_navbar.html.erb b/app/views/layouts/_navbar.html.erb new file mode 100644 index 00000000..33573d07 --- /dev/null +++ b/app/views/layouts/_navbar.html.erb @@ -0,0 +1,4 @@ + diff --git a/config/importmap.rb b/config/importmap.rb index 8dce42d4..46f62ff9 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -5,3 +5,5 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from "app/javascript/controllers", under: "controllers" +pin "bootstrap", to: "bootstrap.min.js", preload: true +pin "@popperjs/core", to: "popper.js", preload: true diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 2eeef966..acde0f73 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -3,6 +3,8 @@ # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = "1.0" +Rails.application.config.assets.precompile += %w(bootstrap.min.js popper.js) + # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path diff --git a/config/routes.rb b/config/routes.rb index a125ef08..21bc7eb6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,5 +6,7 @@ get "up" => "rails/health#show", as: :rails_health_check # Defines the root path route ("/") - # root "posts#index" + root "articles#index" + resources :articles + get "ra/search", to: "articles#search" end diff --git a/db/migrate/20240130021358_create_articles.rb b/db/migrate/20240130021358_create_articles.rb new file mode 100644 index 00000000..55c84675 --- /dev/null +++ b/db/migrate/20240130021358_create_articles.rb @@ -0,0 +1,13 @@ +class CreateArticles < ActiveRecord::Migration[7.1] + def change + create_table :articles do |t| + t.string :title + t.string :content + t.string :text + t.string :author + t.date :date + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 00000000..7dcdba4f --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,24 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.1].define(version: 2024_01_30_021358) do + create_table "articles", force: :cascade do |t| + t.string "title" + t.string "content" + t.string "text" + t.string "author" + t.date "date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + +end