Compare commits
14 commits
main
...
custom-mod
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2f26684b | ||
|
|
a9f72b60af | ||
|
|
46e6014fea | ||
|
|
d6fba7e6e8 | ||
|
|
560a9442a7 | ||
|
|
93b578c3cf | ||
|
|
5c67b91302 | ||
|
|
962915829f | ||
|
|
497744d165 | ||
|
|
2ea386e557 | ||
|
|
820d31b3e5 | ||
|
|
ae1263acff | ||
|
|
fe52089c38 | ||
|
|
411b738c2f |
28 changed files with 680 additions and 100 deletions
|
|
@ -42,32 +42,7 @@ main {
|
|||
align-content: center;
|
||||
}
|
||||
|
||||
.bar-afond {
|
||||
background: black;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.bar-creatif {
|
||||
background: red;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.bar-frigo {
|
||||
background: gray;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.bar-croisiere {
|
||||
background: green;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.bar-en-charge {
|
||||
background: orange;
|
||||
.bar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
|
@ -86,31 +61,6 @@ main {
|
|||
height: 15px;
|
||||
}
|
||||
|
||||
.creatif {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.unknown {
|
||||
border: 2px double grey;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.en-charge {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.frigo-vide {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.croisiere {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.afond {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 30px;
|
||||
}
|
||||
|
|
|
|||
50
app/controllers/day_logs_controller.rb
Normal file
50
app/controllers/day_logs_controller.rb
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
class DayLogsController < ApplicationController
|
||||
before_action :set_day_log, only: [ :update ]
|
||||
|
||||
def edit
|
||||
@day_log = Current.user.day_logs.find_or_initialize_by(day: params[:day])
|
||||
@mood = Current.user.moods.find_by(recorded_at: params[:day].to_date.beginning_of_day..params[:day].to_date.end_of_day)
|
||||
end
|
||||
|
||||
def create
|
||||
@day_log = Current.user.day_logs.build(day_log_params)
|
||||
|
||||
if @day_log.save
|
||||
handle_mood(@day_log.day, params[:day_log][:mode_id])
|
||||
redirect_to root_path
|
||||
else
|
||||
render :new, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @day_log.update(day_log_params)
|
||||
handle_mood(@day_log.day, params[:day_log][:mode_id])
|
||||
redirect_to dashboard_path
|
||||
else
|
||||
render :edit, status: :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_day_log
|
||||
@day_log = Current.user.day_logs.find(params[:id])
|
||||
end
|
||||
|
||||
def handle_mood(day, mode_id)
|
||||
return if mode_id.blank?
|
||||
|
||||
mood = Current.user.moods.find_by(recorded_at: day.beginning_of_day..day.end_of_day)
|
||||
|
||||
if mood
|
||||
mood.update(mode_id: mode_id)
|
||||
else
|
||||
Current.user.moods.create(mode_id: mode_id, recorded_at: day.to_datetime)
|
||||
end
|
||||
end
|
||||
|
||||
def day_log_params
|
||||
params.expect(day_log: [ :day, :info ])
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +1,15 @@
|
|||
module MoodsHelper
|
||||
def mode_for(mood, user)
|
||||
if user.guess?
|
||||
mood[:mode] || mood[:guess] || "unknown"
|
||||
mood[:mode] || mood[:guess] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
|
||||
else
|
||||
mood[:mode] || "unknown"
|
||||
mood[:mode] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
|
||||
end
|
||||
end
|
||||
|
||||
def style_for_mode(mode)
|
||||
style = "background-color: #{mode[:color]};"
|
||||
style += " border: 2px double grey;" if mode[:label] == "unknown"
|
||||
style
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,18 +1,28 @@
|
|||
import { Controller } from '@hotwired/stimulus'
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "image", "modeDay" ];
|
||||
static targets = [ "image", "modeDay", "dayLogLink" ];
|
||||
|
||||
updateDayInfo(event) {
|
||||
const image = this.imageTarget;
|
||||
const modeDay = this.modeDayTarget;
|
||||
const modeDayContent = event.target.dataset.day + ' : ' + event.target.dataset.mode;
|
||||
const day = event.target.dataset.day;
|
||||
const modeDayContent = day + ' : ' + event.target.dataset.mode;
|
||||
image.src = event.target.dataset.image;
|
||||
modeDay.textContent = modeDayContent;
|
||||
event.target.className = "selected-day " + event.target.dataset.mode;
|
||||
|
||||
this.dayLogLinkTarget.href = `/day_logs/edit?day=${day}`
|
||||
|
||||
if (this.lastTarget) {
|
||||
this.lastTarget.className = "day " + this.lastTarget.dataset.mode;
|
||||
}
|
||||
|
||||
this.lastTarget = event.target;
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.lastTarget = null;
|
||||
window.location = "#end";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
app/models/day_log.rb
Normal file
6
app/models/day_log.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class DayLog < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :day, presence: true
|
||||
validates :info, presence: true
|
||||
end
|
||||
4
app/models/mode.rb
Normal file
4
app/models/mode.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
class Mode < ApplicationRecord
|
||||
belongs_to :user
|
||||
has_one_attached :image
|
||||
end
|
||||
|
|
@ -1,3 +1,19 @@
|
|||
class Mood < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :mode
|
||||
|
||||
validates :recorded_at, presence: true
|
||||
validate :unique_per_day_and_user
|
||||
|
||||
private
|
||||
|
||||
def unique_per_day_and_user
|
||||
return unless recorded_at && user
|
||||
|
||||
existing = user.moods.where.not(id: id).any? do |mood|
|
||||
mood.recorded_at.to_date == recorded_at.to_date
|
||||
end
|
||||
|
||||
errors.add(:recorded_at, :taken) if existing
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ class User < ApplicationRecord
|
|||
has_secure_password
|
||||
has_many :sessions, dependent: :destroy
|
||||
has_many :moods, -> { order "recorded_at" }
|
||||
has_many :modes
|
||||
has_many :day_logs
|
||||
|
||||
normalizes :email_address, with: ->(e) { e.strip.downcase }
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ class User < ApplicationRecord
|
|||
MoodCalendarService.generate_calendar(moods)
|
||||
end
|
||||
|
||||
def current_mood
|
||||
def current_mode
|
||||
self.moods.last&.mode || "croisiere"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,11 +1,26 @@
|
|||
# app/services/mood_calendar_service.rb
|
||||
class MoodCalendarService
|
||||
def self.generate_calendar(moods, start_date: nil, end_date: Date.current)
|
||||
# Convertir la relation ActiveRecord en tableau de hash
|
||||
data = moods.order(:recorded_at)
|
||||
.pluck(:mode, :recorded_at)
|
||||
.map { |mode, recorded_at| { mode: mode, recorded_at: recorded_at } }
|
||||
# Preload modes with their image attachments
|
||||
mode_ids = moods.joins(:mode).pluck("modes.id").uniq
|
||||
modes_by_id = Mode.where(id: mode_ids)
|
||||
.with_attached_image
|
||||
.index_by(&:id)
|
||||
|
||||
data = moods.joins(:mode)
|
||||
.order(:recorded_at)
|
||||
.pluck(:recorded_at, "modes.label", "modes.color", "modes.id")
|
||||
.map do |recorded_at, label, color, mode_id|
|
||||
mode = modes_by_id[mode_id]
|
||||
{
|
||||
mode: {
|
||||
label: label,
|
||||
color: color,
|
||||
image_url: mode&.image&.attached? ? Rails.application.routes.url_helpers.rails_blob_url(mode.image, only_path: true) : nil
|
||||
},
|
||||
recorded_at: recorded_at
|
||||
}
|
||||
end # Convertir la relation ActiveRecord en tableau de hash
|
||||
|
||||
if data.empty?
|
||||
start_date = Date.current
|
||||
|
|
@ -43,28 +58,21 @@ class MoodCalendarService
|
|||
# Regrouper par mois avec semaines commençant au premier lundi
|
||||
complete_data.group_by { |d| d[:recorded_at].to_date.beginning_of_month }
|
||||
.map do |month_start, month_data|
|
||||
# Trouver le premier lundi du mois (à partir du 1er du mois)
|
||||
first_monday = month_start
|
||||
first_monday = first_monday.next_occurring(:monday) unless first_monday.monday?
|
||||
|
||||
# Trouver le premier lundi du mois SUIVANT
|
||||
next_month_start = month_start.next_month.beginning_of_month
|
||||
next_first_monday = next_month_start
|
||||
next_first_monday = next_first_monday.next_occurring(:monday) unless next_first_monday.monday?
|
||||
|
||||
# Le dernier jour du mois est le dimanche précédant le premier lundi du mois suivant
|
||||
month_end = next_first_monday - 1.day
|
||||
|
||||
# Créer un hash pour accès rapide aux données de complete_data (qui contient déjà les guess)
|
||||
data_hash = complete_data.index_by { |d| d[:recorded_at].to_date }
|
||||
|
||||
# Générer tous les jours du premier lundi jusqu'au dimanche avant le prochain lundi
|
||||
all_days = (first_monday..month_end).map do |date|
|
||||
# Utiliser directement les données de complete_data qui ont déjà le bon guess
|
||||
data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil }
|
||||
end
|
||||
|
||||
# Grouper par semaines
|
||||
weeks = all_days.group_by { |d| d[:recorded_at].to_date.beginning_of_week(:monday) }
|
||||
.sort_by { |week_start, _| week_start }
|
||||
.map { |_, week_moods| week_moods }
|
||||
|
|
@ -75,4 +83,4 @@ class MoodCalendarService
|
|||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end#
|
||||
|
|
|
|||
20
app/views/day_logs/_form.html.haml
Normal file
20
app/views/day_logs/_form.html.haml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
= form_with model: day_log do |f|
|
||||
- @day_log.errors.full_messages.each do |msg|
|
||||
%p= msg
|
||||
|
||||
.field
|
||||
= f.label :day
|
||||
= f.date_field :day
|
||||
|
||||
.field
|
||||
= f.label :info
|
||||
= f.text_area :info
|
||||
|
||||
.field
|
||||
= label_tag :mode_id, "Mode"
|
||||
= select_tag "day_log[mode_id]",
|
||||
options_from_collection_for_select(Current.user.modes, :id, :label, @mood&.mode_id),
|
||||
include_blank: true
|
||||
|
||||
= f.submit
|
||||
|
||||
3
app/views/day_logs/edit.html.haml
Normal file
3
app/views/day_logs/edit.html.haml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
%h1 Modifier le journal
|
||||
|
||||
= render 'form', day_log: @day_log
|
||||
3
app/views/day_logs/new.html.haml
Normal file
3
app/views/day_logs/new.html.haml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
%h1 Nouveau journal
|
||||
|
||||
= render 'form', day_log: @day_log
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="current-day column is-hidden-mobile">
|
||||
<section class="m-4">
|
||||
<figure class="image has-ratio">
|
||||
<%= image_tag(@user.current_mood + ".jpg", "data-mood-target": "image") %>
|
||||
<%= image_tag(@user.current_mode.image, "data-mood-target": "image") %>
|
||||
</figure>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -12,33 +12,21 @@
|
|||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class=""><strong>Légende</strong></div>
|
||||
</div>
|
||||
<% @user.modes.each do |mode| %>
|
||||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class="bar-frigo mr-1"></div>
|
||||
<div class="">Triste</div>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class="bar-en-charge mr-1"></div>
|
||||
<div class="">En charge</div>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class="bar-croisiere mr-1"></div>
|
||||
<div class="">Croisiere</div>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class="bar-creatif mr-1"></div>
|
||||
<div>Creatif</div>
|
||||
</div>
|
||||
<div class="is-flex is-align-items-center mr-4">
|
||||
<div class="bar-afond mr-1"></div>
|
||||
<div class="">A fond</div>
|
||||
<div class="bar mr-1" style="<%= style_for_mode(mode) %>"></div>
|
||||
<div class=""><%= mode.label %></div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="container mb-4 p-3 has-background-white day-info">
|
||||
<div class="title is-4">
|
||||
<span class="icon"><i class="fa-regular fa-calendar"></i></span>
|
||||
<span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mood %></span>
|
||||
<span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mode.label %></span>
|
||||
<%= link_to "Journal", edit_day_log_for_day_path(day: Date.today), "data-mood-target": "dayLogLink" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs">
|
||||
<div class="is-flex is-flex-direction-row is-flex-wrap-wrap mb-3">
|
||||
<% @user.history.each do |month| %>
|
||||
|
|
@ -49,7 +37,7 @@
|
|||
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3">
|
||||
<% week.each do |mood| %>
|
||||
<% mode = mode_for(mood, @user) %>
|
||||
<div data-image="<%= asset_path(mode + ".jpg") %>" data-mode="<%= mode %>" data-day="<%= l mood[:recorded_at].to_date %>" data-action="click->mood#updateDayInfo" title="<%= l(mood[:recorded_at].to_date) %> : <%= mode %>" class="day <%= mode %>"></div>
|
||||
<div data-image="<%= mode[:image_url] %>" data-mode="<%= mode[:label] %>" data-day="<%= l mood[:recorded_at].to_date %>" data-action="click->mood#updateDayInfo" title="<%= l(mood[:recorded_at].to_date) %> : <%= mode[:label] %>" class="day" style="<%= style_for_mode(mode) %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -21,4 +21,7 @@ Rails.application.routes.draw do
|
|||
patch "/invite/:token", to: "invitations#update", as: :invitation
|
||||
|
||||
get "/moods", to: "moods#index", as: :dashboard
|
||||
|
||||
get "/day_logs/edit", to: "day_logs#edit", as: :edit_day_log_for_day
|
||||
resources :day_logs, only: [ :create, :update ]
|
||||
end
|
||||
|
|
|
|||
11
db/migrate/20260314144625_create_modes.rb
Normal file
11
db/migrate/20260314144625_create_modes.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
class CreateModes < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :modes do |t|
|
||||
t.string :label
|
||||
t.string :color
|
||||
t.references :user, null: false, foreign_key: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
class ReplaceMoodModeStringWithModeReference < ActiveRecord::Migration[8.0]
|
||||
def up
|
||||
# 1. Ajouter la colonne de référence
|
||||
add_reference :moods, :mode, foreign_key: true
|
||||
|
||||
# 2. Pour chaque mood, trouver ou créer le Mode correspondant
|
||||
Mood.find_each do |mood|
|
||||
next if mood.mode.blank?
|
||||
|
||||
mode_record = Mode.find_or_create_by!(label: mood.mode, user_id: mood.user_id) do |m|
|
||||
m.color = "#000000" # couleur par défaut
|
||||
end
|
||||
|
||||
mood.update_column(:mode_id, mode_record.id)
|
||||
end
|
||||
|
||||
# 3. Supprimer l'ancienne colonne string
|
||||
remove_column :moods, :mode, :string
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :moods, :mode, :string
|
||||
|
||||
Mood.find_each do |mood|
|
||||
mood.update_column(:mode, mood.mode&.label)
|
||||
end
|
||||
|
||||
remove_reference :moods, :mode, foreign_key: true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# This migration comes from active_storage (originally 20170806125915)
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
create_table :active_storage_blobs, id: primary_key_type do |t|
|
||||
t.string :key, null: false
|
||||
t.string :filename, null: false
|
||||
t.string :content_type
|
||||
t.text :metadata
|
||||
t.string :service_name, null: false
|
||||
t.bigint :byte_size, null: false
|
||||
t.string :checksum
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :key ], unique: true
|
||||
end
|
||||
|
||||
create_table :active_storage_attachments, id: primary_key_type do |t|
|
||||
t.string :name, null: false
|
||||
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
||||
t.references :blob, null: false, type: foreign_key_type
|
||||
|
||||
if connection.supports_datetime_with_precision?
|
||||
t.datetime :created_at, precision: 6, null: false
|
||||
else
|
||||
t.datetime :created_at, null: false
|
||||
end
|
||||
|
||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
|
||||
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
||||
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
||||
t.string :variation_digest, null: false
|
||||
|
||||
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
|
||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[ primary_key_type, foreign_key_type ]
|
||||
end
|
||||
end
|
||||
13
db/migrate/20260319181607_create_day_logs.rb
Normal file
13
db/migrate/20260319181607_create_day_logs.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class CreateDayLogs < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :day_logs do |t|
|
||||
t.date :day, null: false
|
||||
t.text :info, null: false
|
||||
t.references :user, null: false, foreign_key: true
|
||||
|
||||
t.index :day, unique: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
59
db/schema.rb
generated
59
db/schema.rb
generated
|
|
@ -10,13 +10,61 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_02_26_101258) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2026_03_19_181607) do
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
t.bigint "record_id", null: false
|
||||
t.bigint "blob_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_blobs", force: :cascade do |t|
|
||||
t.string "key", null: false
|
||||
t.string "filename", null: false
|
||||
t.string "content_type"
|
||||
t.text "metadata"
|
||||
t.string "service_name", null: false
|
||||
t.bigint "byte_size", null: false
|
||||
t.string "checksum"
|
||||
t.datetime "created_at", null: false
|
||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
||||
end
|
||||
|
||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
||||
t.bigint "blob_id", null: false
|
||||
t.string "variation_digest", null: false
|
||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||
end
|
||||
|
||||
create_table "day_logs", force: :cascade do |t|
|
||||
t.date "day", null: false
|
||||
t.text "info", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["day"], name: "index_day_logs_on_day", unique: true
|
||||
t.index ["user_id"], name: "index_day_logs_on_user_id"
|
||||
end
|
||||
|
||||
create_table "modes", force: :cascade do |t|
|
||||
t.string "label"
|
||||
t.string "color"
|
||||
t.integer "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["user_id"], name: "index_modes_on_user_id"
|
||||
end
|
||||
|
||||
create_table "moods", force: :cascade do |t|
|
||||
t.string "mode"
|
||||
t.datetime "recorded_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.integer "user_id"
|
||||
t.integer "mode_id"
|
||||
t.index ["mode_id"], name: "index_moods_on_mode_id"
|
||||
t.index ["user_id"], name: "index_moods_on_user_id"
|
||||
end
|
||||
|
||||
|
|
@ -27,7 +75,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_02_26_101258) do
|
|||
t.datetime "updated_at", default: -> { "CURRENT_DATE" }, null: false
|
||||
t.string "chip_id"
|
||||
t.integer "user_id"
|
||||
t.index ["identifier"], name: "index_rfid_tags_on_identifier"
|
||||
t.index ["identifier"], name: "index_rfid_tags_on_identifier", unique: true
|
||||
t.index ["user_id"], name: "index_rfid_tags_on_user_id"
|
||||
end
|
||||
|
||||
|
|
@ -52,6 +100,11 @@ ActiveRecord::Schema[8.0].define(version: 2026_02_26_101258) do
|
|||
t.index ["email_address"], name: "index_users_on_email_address", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "day_logs", "users"
|
||||
add_foreign_key "modes", "users"
|
||||
add_foreign_key "moods", "modes"
|
||||
add_foreign_key "moods", "users"
|
||||
add_foreign_key "rfid_tags", "users"
|
||||
add_foreign_key "sessions", "users"
|
||||
|
|
|
|||
7
spec/factories/day_logs.rb
Normal file
7
spec/factories/day_logs.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
FactoryBot.define do
|
||||
factory :day_log do
|
||||
day { "2026-03-19" }
|
||||
info { "Arrivée à Paris" }
|
||||
user { association :user }
|
||||
end
|
||||
end
|
||||
7
spec/factories/modes.rb
Normal file
7
spec/factories/modes.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
FactoryBot.define do
|
||||
factory :mode do
|
||||
label { "MyString" }
|
||||
color { "MyString" }
|
||||
user { nil }
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
FactoryBot.define do
|
||||
factory :mood do
|
||||
mode { "croisiere" }
|
||||
recorded_at { DateTime.now }
|
||||
recorded_at { DateTime.parse("2026-01-15 10:00:00") }
|
||||
association :user
|
||||
association :mode
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:email_address) { |n| "user#{n}@example.com" }
|
||||
password_digest { BCrypt::Password.create('password123') }
|
||||
sequence(:username) { |n| "user#{n}" }
|
||||
password { "obanonalors" }
|
||||
password_digest { BCrypt::Password.create(password) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
24
spec/models/day_log_spec.rb
Normal file
24
spec/models/day_log_spec.rb
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe DayLog, type: :model do
|
||||
subject { build(:day_log) }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid without a day" do
|
||||
subject.day = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it "is invalid without info" do
|
||||
subject.info = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it "is invalid without a user" do
|
||||
subject.user = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
5
spec/models/mode_spec.rb
Normal file
5
spec/models/mode_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Mode, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
|
|
@ -1,7 +1,46 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe Mood, type: :model do
|
||||
it 'works' do
|
||||
expect(true)
|
||||
describe Mood do
|
||||
let(:user) { create(:user) }
|
||||
let(:mode) { create(:mode, user: user) }
|
||||
|
||||
subject { build(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 10:00:00")) }
|
||||
|
||||
it { is_expected.to be_valid }
|
||||
|
||||
describe "validations" do
|
||||
it "is invalid without a user" do
|
||||
subject.user = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it "is invalid without a mode" do
|
||||
subject.mode = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it "is invalid without a recorded_at" do
|
||||
subject.recorded_at = nil
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "uniqueness per day and user" do
|
||||
it "is invalid if a mood already exists for the same day and user" do
|
||||
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 18:00:00"))
|
||||
expect(subject).not_to be_valid
|
||||
end
|
||||
|
||||
it "is valid if the same day exists but for a different user" do
|
||||
other_user = create(:user)
|
||||
other_mode = create(:mode, user: other_user)
|
||||
create(:mood, user: other_user, mode: other_mode, recorded_at: DateTime.parse("2026-01-15 10:00:00"))
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
|
||||
it "is valid if the same user has a mood on a different day" do
|
||||
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-14 10:00:00"))
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
251
spec/requests/day_logs_spec.rb
Normal file
251
spec/requests/day_logs_spec.rb
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe "DayLogs", type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:mode) { create(:mode, user: user) }
|
||||
let(:day) { Date.parse("2026-01-15") }
|
||||
|
||||
describe "GET /day_logs/edit" do
|
||||
context "when not authenticated" do
|
||||
it "redirects to the login page" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to redirect_to(new_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
before { login_as(user) }
|
||||
|
||||
context "with an existing day_log for the day" do
|
||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
||||
|
||||
it "returns http success" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context "without an existing day_log for the day" do
|
||||
it "returns http success" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
||||
it "does not persist a day_log" do
|
||||
expect {
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
}.not_to change(DayLog, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /day_logs" do
|
||||
context "when not authenticated" do
|
||||
it "redirects to the login page" do
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
||||
expect(response).to redirect_to(new_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
before { login_as(user) }
|
||||
|
||||
context "with valid params" do
|
||||
context "without mode" do
|
||||
it "creates a day_log" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
||||
}.to change(DayLog, :count).by(1)
|
||||
end
|
||||
|
||||
it "does not create a mood" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
||||
}.not_to change(Mood, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context "with mode, no existing mood" do
|
||||
it "creates a day_log" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
|
||||
}.to change(DayLog, :count).by(1)
|
||||
end
|
||||
|
||||
it "creates a mood" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
|
||||
}.to change(Mood, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with mode, existing mood" do
|
||||
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
|
||||
let(:other_mode) { create(:mode, user: user) }
|
||||
|
||||
it "creates a day_log" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
||||
}.to change(DayLog, :count).by(1)
|
||||
end
|
||||
|
||||
it "does not create a new mood" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
||||
}.not_to change(Mood, :count)
|
||||
end
|
||||
|
||||
it "updates the existing mood mode" do
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
||||
expect(existing_mood.reload.mode).to eq(other_mode)
|
||||
end
|
||||
end
|
||||
|
||||
it "redirects after creation" do
|
||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
||||
expect(response).to have_http_status(:redirect)
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "does not create a day_log" do
|
||||
expect {
|
||||
post day_logs_path, params: { day_log: { day: nil, info: nil } }
|
||||
}.not_to change(DayLog, :count)
|
||||
end
|
||||
|
||||
it "returns unprocessable content" do
|
||||
post day_logs_path, params: { day_log: { day: nil, info: nil } }
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /day_logs/:id/edit" do
|
||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
||||
|
||||
context "when not authenticated" do
|
||||
it "redirects to the login page" do
|
||||
get edit_day_log_path(day_log)
|
||||
expect(response).to redirect_to(new_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
before { login_as(user) }
|
||||
|
||||
it "returns http success" do
|
||||
get edit_day_log_path(day_log)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /day_logs/edit" do
|
||||
context "when not authenticated" do
|
||||
it "redirects to the login page" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to redirect_to(new_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
before { login_as(user) }
|
||||
|
||||
context "with an existing day_log for the day" do
|
||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
||||
|
||||
it "returns http success" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context "without an existing day_log for the day" do
|
||||
it "returns http success" do
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
||||
it "does not persist a day_log" do
|
||||
expect {
|
||||
get edit_day_log_for_day_path(day: day)
|
||||
}.not_to change(DayLog, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH /day_logs/:id" do
|
||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
||||
|
||||
context "when not authenticated" do
|
||||
it "redirects to the login page" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
||||
expect(response).to redirect_to(new_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "when authenticated" do
|
||||
before { login_as(user) }
|
||||
|
||||
context "with valid params" do
|
||||
context "without mode" do
|
||||
it "updates the day_log" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
||||
expect(day_log.reload.info).to eq("Nouvelle info")
|
||||
end
|
||||
|
||||
it "does not create a mood" do
|
||||
expect {
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
||||
}.not_to change(Mood, :count)
|
||||
end
|
||||
|
||||
it "redirects to dashboard" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
||||
expect(response).to redirect_to(dashboard_path)
|
||||
end
|
||||
end
|
||||
|
||||
context "with mode, no existing mood" do
|
||||
it "creates a mood" do
|
||||
expect {
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: mode.id } }
|
||||
}.to change(Mood, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with mode, existing mood" do
|
||||
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
|
||||
let(:other_mode) { create(:mode, user: user) }
|
||||
|
||||
it "does not create a new mood" do
|
||||
expect {
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
|
||||
}.not_to change(Mood, :count)
|
||||
end
|
||||
|
||||
it "updates the existing mood mode" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
|
||||
expect(existing_mood.reload.mode).to eq(other_mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid params" do
|
||||
it "does not update the day_log" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: nil } }
|
||||
expect(day_log.reload.info).not_to be_nil
|
||||
end
|
||||
|
||||
it "returns unprocessable content" do
|
||||
patch day_log_path(day_log), params: { day_log: { info: nil } }
|
||||
expect(response).to have_http_status(:unprocessable_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
12
spec/support/authentication_helper.rb
Normal file
12
spec/support/authentication_helper.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module AuthenticationHelper
|
||||
def login_as(user)
|
||||
post session_path, params: {
|
||||
email_address: user.email_address,
|
||||
password: user.password
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include AuthenticationHelper, type: :request
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue