feat: added counts and instant update and destroy
Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
parent
68deb0f810
commit
d07857c011
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
class Count::HomeController < ApplicationController
|
|
||||||
before_action :authenticate_user!
|
|
||||||
|
|
||||||
def index
|
|
||||||
end
|
|
||||||
end
|
|
51
app/controllers/count_controller.rb
Normal file
51
app/controllers/count_controller.rb
Normal 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
|
@ -1,4 +1,5 @@
|
|||||||
class HomeController < ApplicationController
|
class HomeController < ApplicationController
|
||||||
def index
|
def index
|
||||||
|
@counts = Total.all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
2
app/javascript/add_bootstrap.js
Normal file
2
app/javascript/add_bootstrap.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import * as bootstrap from "bootstrap"
|
||||||
|
window.bootstrap = require('bootstrap/dist/js/bootstrap.bundle.js');
|
3
app/javascript/add_jquery.js
Normal file
3
app/javascript/add_jquery.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import jquery from 'jquery'
|
||||||
|
window.jQuery = jquery
|
||||||
|
window.$ = jquery
|
@ -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"
|
||||||
|
|
||||||
|
@ -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
30
app/models/total.rb
Normal 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
|
4
app/views/count/edit.html.erb
Normal file
4
app/views/count/edit.html.erb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
<h2 class="mt-2"><%= t("fields.edit") %></h2>
|
||||||
|
|
||||||
|
<%= render "count/shared/form" %>
|
@ -1,2 +0,0 @@
|
|||||||
<h1>Count::Home#index</h1>
|
|
||||||
<p>Find me in app/views/count/home/index.html.erb</p>
|
|
22
app/views/count/index.html.erb
Normal file
22
app/views/count/index.html.erb
Normal 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>
|
4
app/views/count/new.html.erb
Normal file
4
app/views/count/new.html.erb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
<h2 class="mt-2"><%= t("fields.new") %></h2>
|
||||||
|
|
||||||
|
<%= render "count/shared/form" %>
|
17
app/views/count/shared/_form.html.erb
Normal file
17
app/views/count/shared/_form.html.erb
Normal 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 %>
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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"
|
12
config/locales/en/flashes.en.yml
Normal file
12
config/locales/en/flashes.en.yml
Normal 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."
|
@ -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"
|
||||||
|
12
config/locales/fr/flashes.fr.yml
Normal file
12
config/locales/fr/flashes.fr.yml
Normal 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."
|
@ -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"
|
||||||
|
|
||||||
|
10
db/migrate/20230204135557_create_totals.rb
Normal file
10
db/migrate/20230204135557_create_totals.rb
Normal 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
10
db/schema.rb
generated
@ -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
|
||||||
|
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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"
|
||||||
|
Loading…
Reference in New Issue
Block a user