feat: added counts and instant update and destroy

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2023-02-05 21:01:07 +01:00
parent 68deb0f810
commit d07857c011
No known key found for this signature in database
GPG Key ID: 0C87282F76E61283
26 changed files with 283 additions and 21 deletions

View File

@ -1,4 +1,18 @@
module ApplicationCable module ApplicationCable
class Connection < ActionCable::Connection::Base class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if (verified_user = env['warden'].user)
verified_user
else
nil
end
end
end end
end end

View File

@ -1,9 +1,40 @@
class CountChannel < ApplicationCable::Channel class CountChannel < ApplicationCable::Channel
def subscribed def subscribed
# stream_from "some_channel" stream_from "counts"
end end
def unsubscribed def unsubscribed
# Any cleanup needed when channel is unsubscribed # Any cleanup needed when channel is unsubscribed
end end
def plus_one(params)
unless current_user.nil?
total = Total.find(params["id"])
unless total.nil?
total.update(count: total.count + 1)
end
end
end
def minus_one(params)
unless current_user.nil?
total = Total.find(params["id"])
unless total.nil?
total.update(count: total.count - 1)
end
end
end
def self.create(total)
end
def self.update(total)
ActionCable.server.broadcast("counts", {"update": total})
end
def self.destroy(total)
ActionCable.server.broadcast("counts", {"destroy": total.id})
end
end end

View File

@ -1,6 +0,0 @@
class Count::HomeController < ApplicationController
before_action :authenticate_user!
def index
end
end

View File

@ -0,0 +1,51 @@
class CountController < ApplicationController
before_action :authenticate_user!
def index
@counts = Total.all
end
def edit
@count = Total.find(params[:id])
end
def destroy
if Total.find(params[:id]).destroy
flash[:notice] = I18n.translate("flashes.count.destroy.success")
redirect_to count_index_path
else
flash[:alert] = I18n.translate("flashes.count.destroy.fail")
redirect_to edit_count_path
end
end
def new
@count = Total.new
end
def update
if Total.find(params[:id]).update(validated_params(params))
flash[:notice] = I18n.translate("flashes.count.update.success")
redirect_to count_index_path
else
flash[:alert] = I18n.translate("flashes.count.update.fail")
redirect_to edit_count_path
end
end
def create
if Total.new(validated_params(params)).save
flash[:notice] = I18n.translate("flashes.count.create.success")
redirect_to count_index_path
else
flash[:alert] = I18n.translate("flashes.count.create.fail")
redirect_to new_count_path
end
end
private
def validated_params(params)
params.require(:total).permit([:name, :count])
end
end

View File

@ -1,4 +1,5 @@
class HomeController < ApplicationController class HomeController < ApplicationController
def index def index
@counts = Total.all
end end
end end

View File

@ -0,0 +1,2 @@
import * as bootstrap from "bootstrap"
window.bootstrap = require('bootstrap/dist/js/bootstrap.bundle.js');

View File

@ -0,0 +1,3 @@
import jquery from 'jquery'
window.jQuery = jquery
window.$ = jquery

View File

@ -1,5 +1,7 @@
// Entry point for the build script in your package.json // Entry point for the build script in your package.json
import "./add_jquery"
import "@hotwired/turbo-rails" import "@hotwired/turbo-rails"
import "./controllers" import "./controllers"
import * as bootstrap from "bootstrap" import * as bootstrap from "bootstrap"
import "./channels" import "./channels"

View File

@ -1,6 +1,6 @@
import consumer from "./consumer" import consumer from "./consumer"
consumer.subscriptions.create("CountChannel", { const count_channel = consumer.subscriptions.create("CountChannel", {
connected() { connected() {
// Called when the subscription is ready for use on the server // Called when the subscription is ready for use on the server
console.warn("Connected to CountChannel."); console.warn("Connected to CountChannel.");
@ -12,5 +12,26 @@ consumer.subscriptions.create("CountChannel", {
received(data) { received(data) {
// Called when there's incoming data on the websocket for this channel // Called when there's incoming data on the websocket for this channel
} console.log(data)
if (data.update !== undefined)
this.update(data.update)
if (data.destroy !== undefined)
this.destroy(data.destroy)
},
update(data) {
$("[data-count-id='"+ data.id + "'] .count").text(data.count)
$("[data-count-id='"+ data.id + "'] .name").text(data.name)
},
destroy(data) {
$("[data-count-id='"+ data + "']").remove()
},
}); });
$(document).on('turbo:load', function() {
if (count_channel !== undefined && !count_channel.consumer.connection.disconnected) {
$("")
}
})

30
app/models/total.rb Normal file
View File

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: totals
#
# id :integer not null, primary key
# count :integer default(0), not null
# name :text not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_totals_on_name (name)
#
class Total < ApplicationRecord
validates_uniqueness_of :name
validates_length_of :name, in: 1..32
after_save do
CountChannel.create(self)
end
after_update do
CountChannel.update(self)
end
after_destroy do
CountChannel.destroy(self)
end
end

View File

@ -0,0 +1,4 @@
<h2 class="mt-2"><%= t("fields.edit") %></h2>
<%= render "count/shared/form" %>

View File

@ -1,2 +0,0 @@
<h1>Count::Home#index</h1>
<p>Find me in app/views/count/home/index.html.erb</p>

View File

@ -0,0 +1,22 @@
<div class="mt-2 row row-cols-4">
<% @counts.each { |c| %>
<div class="col mt-1 mb-2 text-center" data-count-id="<%= c.id %>">
<div class="row">
<div class="col text-end">
<button type="button" class="btn btn-lg btn-outline-danger fw-bold my-auto fs-3 minus">-</button>
</div>
<span class="col fw-bold fs-1 count"><%= c.count %></span>
<div class="col text-start">
<button type="button" class="btn btn-lg btn-outline-success fw-bold my-auto fs-3 plus">+</button>
</div>
</div>
<div class="fw-light fs-4">
<span><%= c.name %></span>
<span><%= link_to "", edit_count_path(c), class: "btn btn-sm btn-primary bi-pen-fill" %></span>
</div>
</div>
<% } %>
</div>
<div class="fixed-bottom text-end m-5">
<%= link_to "", new_count_path, class: "btn btn-lg btn-success fs-3 bi-plus-lg" %>
</div>

View File

@ -0,0 +1,4 @@
<h2 class="mt-2"><%= t("fields.new") %></h2>
<%= render "count/shared/form" %>

View File

@ -0,0 +1,17 @@
<div class="mt-2 mb-4">
<%= form_for @count, url: @count.id ? count_path : count_index_path, class: "row" do |f| %>
<div class="col-2">
<%= f.label :name, t("fields.name") %>
<%= f.text_field :name, class: "form-control" %>
</div>
<div class="col-2 mt-2 mb-2">
<%= f.label :count, t("fields.count") %>
<%= f.number_field :count, class: "form-control" %>
</div>
<%= f.submit t("fields.save"), class: "btn btn-primary" %>
<% end %>
</div>
<% if @count.id %>
<%= button_to "", count_path(@count), method: :delete, class: "bi-trash-fill btn btn-danger" %>
<% end %>

View File

@ -1,2 +1,10 @@
<h1>Home#index</h1> <div class="mt-2 row row-cols-4">
<p>Find me in app/views/home/index.html.erb</p> <% @counts.each { |c| %>
<div class="col text-center" data-count-id="<%= c.id %>">
<span class="fw-bold fs-1 count"><%= c.count %></span>
<div class="fw-light fs-4 name">
<%= c.name %>
</div>
</div>
<% } %>
</div>

View File

@ -18,7 +18,7 @@
<ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"> <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
<li><%= link_to t("fields.home"), root_path, class: "nav-link px-2 #{current_page?(root_path) ? "text-secondary" : "text-white"}" %></li> <li><%= link_to t("fields.home"), root_path, class: "nav-link px-2 #{current_page?(root_path) ? "text-secondary" : "text-white"}" %></li>
<% if current_user %> <% if current_user %>
<li><%= link_to t("fields.count"), count_root_path, class: "nav-link px-2 #{current_page?(count_root_path) ? "text-secondary" : "text-white"}" %></li> <li><%= link_to t("fields.count"), count_index_path, class: "nav-link px-2 #{current_page?(count_index_path) ? "text-secondary" : "text-white"}" %></li>
<li><%= link_to t("fields.profile"), profile_root_path, class: "nav-link px-2 #{current_page?(profile_root_path) ? "text-secondary" : "text-white"}" %></li> <li><%= link_to t("fields.profile"), profile_root_path, class: "nav-link px-2 #{current_page?(profile_root_path) ? "text-secondary" : "text-white"}" %></li>
<% end %> <% end %>
</ul> </ul>
@ -37,12 +37,17 @@
<div class="container"> <div class="container">
<% if notice %> <% if notice %>
<p class="notice"><%= notice %></p> <div class="alert alert-primary" role="alert">
<%= notice %>
</div>
<% end %> <% end %>
<% if alert %> <% if alert %>
<p class="alert"><%= alert %></p> <div class="alert alert-danger" role="alert">
<%= alert %>
</div>
<% end %> <% end %>
<%= yield %> <%= yield %>
</div> </div>
</body> </body>

View File

@ -5,3 +5,7 @@ en:
logout: "Logout" logout: "Logout"
count: "Count" count: "Count"
profile: "Profile" profile: "Profile"
edit: "Edit"
new: "New"
name: "Name"
save: "Save"

View File

@ -0,0 +1,12 @@
en:
flashes:
count:
update:
success: "Count has been successfully saved."
fail: "The update failed for the count."
create:
success: "Count creation was successful."
fail: "Count creation failed."
destroy:
fail: "Count deletion has failed."
success: "Count has been deleted successfully."

View File

@ -5,3 +5,7 @@ fr:
logout: "Se déconnecter" logout: "Se déconnecter"
count: "Compte" count: "Compte"
profile: "Profil" profile: "Profil"
edit: "Éditer"
new: "Nouveau"
name: "Nom"
save: "Sauvegarder"

View File

@ -0,0 +1,12 @@
fr:
flashes:
count:
update:
success: "Le compte a bien été sauvegardé."
fail: "La mise à jour du compte a échoué."
create:
success: "La création du compte a été faite avec succès."
fail: "La création du compte a échoué."
destroy:
fail: "La suppression du compte a échoué."
success: "Le compte a été supprimé avec succès."

View File

@ -1,11 +1,10 @@
Rails.application.routes.draw do Rails.application.routes.draw do
namespace :profile do namespace :profile do
root "home#index" root "home#index"
end end
namespace :count do resources :count, except: [:show]
root "home#index"
end
root "home#index" root "home#index"

View File

@ -0,0 +1,10 @@
class CreateTotals < ActiveRecord::Migration[7.0]
def change
create_table :totals do |t|
t.integer :count, null: false, default: 0
t.text :name, index: true, null: false
t.timestamps
end
end
end

10
db/schema.rb generated
View File

@ -10,7 +10,15 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_02_04_125140) do ActiveRecord::Schema[7.0].define(version: 2023_02_04_135557) do
create_table "totals", force: :cascade do |t|
t.integer "count", default: 0, null: false
t.text "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_totals_on_name"
end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false t.string "encrypted_password", default: "", null: false

View File

@ -8,6 +8,7 @@
"@rails/actioncable": "^7.0.4-2", "@rails/actioncable": "^7.0.4-2",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"bootstrap-icons": "^1.10.3", "bootstrap-icons": "^1.10.3",
"jquery": "^3.6.3",
"esbuild": "^0.17.5", "esbuild": "^0.17.5",
"sass": "^1.58.0" "sass": "^1.58.0"
}, },

View File

@ -266,6 +266,11 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
jquery@^3.6.3:
version "3.6.3"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.3.tgz#23ed2ffed8a19e048814f13391a19afcdba160e6"
integrity sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==
normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"