diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index ddd546a0b..4028c22a6 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,4 +1,4 @@ //= link_tree ../images -//= link_directory ../stylesheets .css //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js +//= link_tree ../builds diff --git a/app/assets/stylesheets/application.bootstrap.scss b/app/assets/stylesheets/application.bootstrap.scss new file mode 100644 index 000000000..e8d5a1c9e --- /dev/null +++ b/app/assets/stylesheets/application.bootstrap.scss @@ -0,0 +1,2 @@ +@import 'bootstrap/scss/bootstrap'; +@import 'bootstrap-icons/font/bootstrap-icons'; \ 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 000000000..852275863 --- /dev/null +++ b/app/controllers/articles_controller.rb @@ -0,0 +1,69 @@ +class ArticlesController < ApplicationController + # Ensures that the set_article method is called before the specified actions + before_action :set_article, only: %i[show edit update destroy] + + # Displays a list of articles, filtered by title if a query parameter is present + def index + if params[:query].present? + @articles = Article.where("title LIKE ?", params[:query]) + else + @articles = Article.all + end + end + + # Displays the details of a specific article + def show + end + + # Initializes a new article instance for creation + def new + @article = Article.new + end + + # Renders the form for editing an existing article + def edit + end + + # Creates a new article based on parameters, and renders turbo_stream if successful + def create + @article = Article.new(article_params) + if @article.save + respond_to do |format| + format.turbo_stream + end + else + render :new, status: :unprocessable_entity + end + end + + # Updates an existing article with the provided parameters, redirects on success + def update + if @article.update(article_params) + respond_to do |format| + format.html { redirect_to article_url(@article), notice: "Article was successfully updated." } + end + else + format.html { render :edit, status: :unprocessable_entity } + end + end + + # Destroys the specified article instance, rendering turbo_stream + def destroy + @article.destroy! + + respond_to do |format| + format.turbo_stream + end + end + + private + # Sets the @article instance variable based on the provided ID parameter + def set_article + @article = Article.find(params[:id]) + end + + # Defines the permitted parameters for article creation and updates + def article_params + params.require(:article).permit(:title, :content, :author, :date) + end +end diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb new file mode 100644 index 000000000..13fac9077 --- /dev/null +++ b/app/helpers/articles_helper.rb @@ -0,0 +1,9 @@ +module ArticlesHelper + def display_errors_for_field(object, field) + if object.errors.any? + object.errors.full_messages_for(field).each do |message| + concat content_tag(:div, message, class: 'text-danger') + end + end + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index 0d7b49404..d16fcbe2f 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,3 +1,4 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "@hotwired/turbo-rails" import "controllers" +import * as bootstrap from "bootstrap" diff --git a/app/models/article.rb b/app/models/article.rb new file mode 100644 index 000000000..53a93dfa7 --- /dev/null +++ b/app/models/article.rb @@ -0,0 +1,10 @@ +class Article < ApplicationRecord + # Validates presence of title and content fields + validates :title, presence: true + validates :content, presence: true + + # Defines a class method to search for articles based on a query + def self.search(query) + where("title LIKE :query OR content LIKE :query", query: "%#{query}%") + end +end diff --git a/app/views/articles/_article.html.erb b/app/views/articles/_article.html.erb new file mode 100644 index 000000000..320abd1f4 --- /dev/null +++ b/app/views/articles/_article.html.erb @@ -0,0 +1,28 @@ +<%= turbo_frame_tag article do %> +
+

+ Title: + <%= link_to article.title, article, data: { turbo_frame: '_top'} %> +

+ +

+ Content: + <%= article.content %> +

+ +

+ Author: + <%= article.author %> +

+ +

+ Date: + <%= article.date %> +

+ +
+ <%= link_to 'Edit', edit_article_path(article), class: 'btn btn-success w-100' %> + <%= button_to 'Delete', article_path(article), method: :delete, class: 'btn btn-danger'%> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/articles/_form.html.erb b/app/views/articles/_form.html.erb new file mode 100644 index 000000000..5a0d9b689 --- /dev/null +++ b/app/views/articles/_form.html.erb @@ -0,0 +1,30 @@ +
+ <%= form_with(model: article) do |form| %> + +
+ <%= form.label :title, style: "display: block" %> + <%= form.text_field :title, class: 'form-control' %> + <% display_errors_for_field(@article, :title) %> +
+ +
+ <%= form.label :content, style: "display: block" %> + <%= form.text_field :content, class: 'form-control' %> + <% display_errors_for_field(@article, :content) %> +
+ +
+ <%= form.label :author, style: "display: block" %> + <%= form.text_field :author, class: 'form-control' %> +
+ +
+ <%= form.hidden_field :date, class: 'form-control', value: Time.now %> +
+ +
+ <%= form.submit class: 'btn btn-success mt-2 w-100' %> + <%= link_to 'Cancel', root_path, class: 'btn btn-danger mt-2'%> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/articles/create.turbo_stream.erb b/app/views/articles/create.turbo_stream.erb new file mode 100644 index 000000000..76006b4e6 --- /dev/null +++ b/app/views/articles/create.turbo_stream.erb @@ -0,0 +1,5 @@ +<%= turbo_stream.append "articles" do%> + <%= render 'articles/article', article: @article%> +<% end %> + +<%= turbo_stream.update Article.new, '' %> \ No newline at end of file diff --git a/app/views/articles/destroy.turbo_stream.erb b/app/views/articles/destroy.turbo_stream.erb new file mode 100644 index 000000000..55068f4fb --- /dev/null +++ b/app/views/articles/destroy.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.remove @article %> \ 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 000000000..8e76ed0a8 --- /dev/null +++ b/app/views/articles/edit.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag @article do %> + <%= render "form", article: @article %> +<% end %> \ No newline at end of file diff --git a/app/views/articles/index.html.erb b/app/views/articles/index.html.erb new file mode 100644 index 000000000..9c9fcc296 --- /dev/null +++ b/app/views/articles/index.html.erb @@ -0,0 +1,24 @@ +

Encyclopedia

+

CRUD with Rails

+
+ +<%= form_with(url: articles_path, method: :get, data: { turbo_frame: "article", turbo_action: 'advance' }) do |form| %> +
+ <%= form.text_field :query, class: 'form-control' %> + <%= form.submit class: 'btn btn-success' %> + <% if params[:query].present? %> + <%= link_to 'Clear', articles_path, class: 'btn btn-danger' %> + <% end %> +
+<% end %> + +
+ +<%= link_to 'Add Article', new_article_path, data: { turbo_frame: dom_id(Article.new) }, class: 'btn btn-dark' %> +<%= turbo_frame_tag Article.new %> + +
+ <% @articles.each do |article| %> + <%= render article %> + <% end %> +
diff --git a/app/views/articles/new.html.erb b/app/views/articles/new.html.erb new file mode 100644 index 000000000..7c07425a1 --- /dev/null +++ b/app/views/articles/new.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag @article do%> + <%= render "form", article: @article %> +<% end %> diff --git a/app/views/articles/show.html.erb b/app/views/articles/show.html.erb new file mode 100644 index 000000000..dad7544e3 --- /dev/null +++ b/app/views/articles/show.html.erb @@ -0,0 +1 @@ +<%= render @article %> \ No newline at end of file diff --git a/app/views/articles/update.turbo_stream.erb b/app/views/articles/update.turbo_stream.erb new file mode 100644 index 000000000..7587c7797 --- /dev/null +++ b/app/views/articles/update.turbo_stream.erb @@ -0,0 +1 @@ +<%= turbo_stream.update @article %> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 812bfb90f..052dfc3e9 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -11,6 +11,8 @@ - <%= yield %> +
+ <%= yield %> +
diff --git a/config/importmap.rb b/config/importmap.rb index 8dce42d40..a87a8f424 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -5,3 +5,4 @@ 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" diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 2eeef966f..139b45cdb 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,8 +5,12 @@ # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path +Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js") +Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap-icons/font") +Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js") # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. # Rails.application.config.assets.precompile += %w( admin.js admin.css ) +Rails.application.config.assets.precompile << "bootstrap.min.js" \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index a125ef085..7980624ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,10 +1,6 @@ Rails.application.routes.draw do - # 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. + root "articles#index" + get '/search', to: "articles#search" + resources :articles get "up" => "rails/health#show", as: :rails_health_check - - # Defines the root path route ("/") - # root "posts#index" end diff --git a/db/migrate/20240129153043_create_articles.rb b/db/migrate/20240129153043_create_articles.rb new file mode 100644 index 000000000..3305b4bf6 --- /dev/null +++ b/db/migrate/20240129153043_create_articles.rb @@ -0,0 +1,11 @@ +class CreateArticles < ActiveRecord::Migration[7.1] + def change + create_table :articles do |t| + t.string :title + t.string :content + t.string :author + + t.timestamps + end + end +end diff --git a/db/migrate/20240129173106_add_date_to_article.rb b/db/migrate/20240129173106_add_date_to_article.rb new file mode 100644 index 000000000..da07046cc --- /dev/null +++ b/db/migrate/20240129173106_add_date_to_article.rb @@ -0,0 +1,5 @@ +class AddDateToArticle < ActiveRecord::Migration[7.1] + def change + add_column :articles, :date, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..8131c304f --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,23 @@ +# 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_29_173106) do + create_table "articles", force: :cascade do |t| + t.string "title" + t.string "content" + t.string "author" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.datetime "date" + end + +end diff --git a/test/fixtures/articles.yml b/test/fixtures/articles.yml new file mode 100644 index 000000000..ee2868227 --- /dev/null +++ b/test/fixtures/articles.yml @@ -0,0 +1,11 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# one: +# title: MyString +# content: MyString +# author: MyString + +# two: +# title: MyString +# content: MyString +# author: MyString