mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Add trips model and scaffold controller
This commit is contained in:
parent
cb1665d8be
commit
198bf3128a
18 changed files with 434 additions and 1 deletions
51
app/controllers/trips_controller.rb
Normal file
51
app/controllers/trips_controller.rb
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TripsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_trip, only: %i[show edit update destroy]
|
||||
|
||||
def index
|
||||
@trips = current_user.trips
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def new
|
||||
@trip = Trip.new
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
||||
def create
|
||||
@trip = current_user.trips.build(trip_params)
|
||||
|
||||
if @trip.save
|
||||
redirect_to @trip, notice: 'Trip was successfully created.'
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @trip.update(trip_params)
|
||||
redirect_to @trip, notice: 'Trip was successfully updated.', status: :see_other
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@trip.destroy!
|
||||
redirect_to trips_url, notice: 'Trip was successfully destroyed.', status: :see_other
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_trip
|
||||
@trip = current_user.trips.find(params[:id])
|
||||
end
|
||||
|
||||
def trip_params
|
||||
params.require(:trip).permit(:name, :started_at, :ended_at, :notes)
|
||||
end
|
||||
end
|
||||
2
app/helpers/trips_helper.rb
Normal file
2
app/helpers/trips_helper.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
module TripsHelper
|
||||
end
|
||||
7
app/models/trip.rb
Normal file
7
app/models/trip.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Trip < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :name, :started_at, :ended_at, presence: true
|
||||
end
|
||||
|
|
@ -15,6 +15,7 @@ class User < ApplicationRecord
|
|||
has_many :visits, dependent: :destroy
|
||||
has_many :points, through: :imports
|
||||
has_many :places, through: :visits
|
||||
has_many :trips, dependent: :destroy
|
||||
|
||||
after_create :create_api_key
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url(status: :confirmed), class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Places<sup>β</sup>'.html_safe, places_url, class: "#{active_class?(places_url)}" %></li>
|
||||
<li><%= link_to 'Trips<sup>β</sup>'.html_safe, trips_url, class: "#{active_class?(trips_url)}" %></li>
|
||||
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||
<li><%= link_to 'Exports', exports_url, class: "#{active_class?(exports_url)}" %></li>
|
||||
</ul>
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
<li><%= link_to 'Stats', stats_url, class: "#{active_class?(stats_url)}" %></li>
|
||||
<li><%= link_to 'Visits<sup>β</sup>'.html_safe, visits_url(status: :confirmed), class: "#{active_class?(visits_url)}" %></li>
|
||||
<li><%= link_to 'Places<sup>β</sup>'.html_safe, places_url, class: "#{active_class?(places_url)}" %></li>
|
||||
<li><%= link_to 'Trips', trips_url, class: "#{active_class?(trips_url)}" %></li>
|
||||
<li><%= link_to 'Imports', imports_url, class: "#{active_class?(imports_url)}" %></li>
|
||||
<li><%= link_to 'Exports', exports_url, class: "#{active_class?(exports_url)}" %></li>
|
||||
</ul>
|
||||
|
|
|
|||
39
app/views/trips/_form.html.erb
Normal file
39
app/views/trips/_form.html.erb
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<%= form_with(model: trip, class: "contents") do |form| %>
|
||||
<% if trip.errors.any? %>
|
||||
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
|
||||
<h2><%= pluralize(trip.errors.count, "error") %> prohibited this trip from being saved:</h2>
|
||||
|
||||
<ul>
|
||||
<% trip.errors.each do |error| %>
|
||||
<li><%= error.full_message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex flex-col lg:flex-row gap-4 mt-4">
|
||||
<div class="form-control w-full lg:w-1/2">
|
||||
<%= form.label :name %>
|
||||
<%= form.text_field :name, class: 'input input-bordered' %>
|
||||
</div>
|
||||
<div class="flex flex-col lg:flex-row lg:w-1/2 gap-4">
|
||||
<div class="form-control w-full lg:w-1/2">
|
||||
<%= form.label :started_at %>
|
||||
<%= form.datetime_field :started_at, include_seconds: false, class: 'input input-bordered', value: trip.started_at %>
|
||||
</div>
|
||||
<div class="form-control w-full lg:w-1/2">
|
||||
<%= form.label :ended_at %>
|
||||
<%= form.datetime_field :ended_at, include_seconds: false, class: 'input input-bordered', value: trip.ended_at %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full mt-4 mb-4">
|
||||
<%= form.label :notes %>
|
||||
<%= form.text_area :notes, class: 'textarea textarea-bordered w-full', rows: 10 %>
|
||||
</div>
|
||||
|
||||
<div class="inline mb-4">
|
||||
<%= form.submit class: "rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer" %>
|
||||
</div>
|
||||
<% end %>
|
||||
2
app/views/trips/_trip.html.erb
Normal file
2
app/views/trips/_trip.html.erb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<div id="<%= dom_id trip %>">
|
||||
</div>
|
||||
8
app/views/trips/edit.html.erb
Normal file
8
app/views/trips/edit.html.erb
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<div class="mx-auto md:w-2/3 w-full">
|
||||
<h1 class="font-bold text-4xl">Editing trip</h1>
|
||||
|
||||
<%= render "form", trip: @trip %>
|
||||
|
||||
<%= link_to "Show this trip", @trip, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||
<%= link_to "Back to trips", trips_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||
</div>
|
||||
48
app/views/trips/index.html.erb
Normal file
48
app/views/trips/index.html.erb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<% content_for :title, 'Trips' %>
|
||||
|
||||
<div class="w-full">
|
||||
<div id="trips" class="min-w-full">
|
||||
<% if @trips.empty? %>
|
||||
<div class="hero min-h-80 bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<h1 class="text-5xl font-bold">Hello there!</h1>
|
||||
<p class="py-6">
|
||||
Here you'll find your trips, but now there are none. Let's <%= link_to 'create one', new_trip_path, class: 'link' %>!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex justify-center my-5">
|
||||
<div class='flex'>
|
||||
<%= paginate @trips %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Started at</th>
|
||||
<th>Ended at</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
data-controller="trips"
|
||||
data-trips-target="index"
|
||||
data-user-id="<%= current_user.id %>"
|
||||
>
|
||||
<% @trips.each do |trip| %>
|
||||
<tr data-trip-id="<%= trip.id %>" id="trip-<%= trip.id %>">
|
||||
<td><%= link_to trip.name, trip, class: 'underline hover:no-underline' %></td>
|
||||
<td><%= trip.started_at.strftime("%d.%m.%Y, %H:%M") %></td>
|
||||
<td><%= trip.ended_at.strftime("%d.%m.%Y, %H:%M") %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
9
app/views/trips/new.html.erb
Normal file
9
app/views/trips/new.html.erb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<% content_for :title, 'New trip' %>
|
||||
|
||||
<div class="mx-auto md:w-2/3 w-full">
|
||||
<h1 class="font-bold text-4xl">New trip</h1>
|
||||
|
||||
<%= render "form", trip: @trip %>
|
||||
|
||||
<%= link_to "Back to trips", trips_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
|
||||
</div>
|
||||
64
app/views/trips/show.html.erb
Normal file
64
app/views/trips/show.html.erb
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<% content_for :title, @trip.name %>
|
||||
|
||||
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
||||
<!-- Header Section -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold mb-2"><%= @trip.name %></h1>
|
||||
<p class="text-lg text-base-content/60">Countries visited: [Placeholder]</p>
|
||||
</div>
|
||||
|
||||
<!-- Map and Description Section -->
|
||||
<div class="bg-base-100 mb-8">
|
||||
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="w-full">
|
||||
<div id="map" class="w-full h-96 bg-base-200">Map Placeholder</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="prose">
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Photos Grid Section -->
|
||||
<% (1..12).each_slice(4) do |slice| %>
|
||||
<div class="flex flex-row gap-4 mt-4 justify-center">
|
||||
<% slice.each do %>
|
||||
<div class="aspect-square rounded-box overflow-hidden bg-base-200 w-32">
|
||||
<img src="https://placehold.co/128x128" alt="Photo Placeholder"
|
||||
class="w-32 object-cover">
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<%= link_to "More photos on Immich", "#", class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons Section -->
|
||||
<div class="bg-base-100 items-center">
|
||||
<div class="flex flex-wrap gap-2 justify-center">
|
||||
<%= link_to "Edit this trip", edit_trip_path(@trip), class: "btn" %>
|
||||
<%= link_to "Destroy this trip",
|
||||
trip_path(@trip),
|
||||
data: {
|
||||
turbo_confirm: "Are you sure? This action will delete all points imported with this file",
|
||||
turbo_method: :delete
|
||||
},
|
||||
class: "btn" %>
|
||||
<%= link_to "Back to trips", trips_path, class: "btn" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
require 'sidekiq/web'
|
||||
|
||||
Rails.application.routes.draw do
|
||||
resources :trips
|
||||
mount ActionCable.server => '/cable'
|
||||
mount Rswag::Api::Engine => '/api-docs'
|
||||
mount Rswag::Ui::Engine => '/api-docs'
|
||||
|
|
|
|||
15
db/migrate/20241127161621_create_trips.rb
Normal file
15
db/migrate/20241127161621_create_trips.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateTrips < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :trips do |t|
|
||||
t.string :name, null: false
|
||||
t.datetime :started_at, null: false
|
||||
t.datetime :ended_at, null: false
|
||||
t.text :notes
|
||||
t.references :user, null: false, foreign_key: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
30
db/schema.rb
generated
30
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2024_10_30_152025) do
|
||||
ActiveRecord::Schema[7.2].define(version: 2024_11_27_161621) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
|
@ -175,6 +175,32 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_30_152025) do
|
|||
t.index ["year"], name: "index_stats_on_year"
|
||||
end
|
||||
|
||||
create_table "trips", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.datetime "started_at", null: false
|
||||
t.datetime "ended_at", null: false
|
||||
t.text "notes"
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["user_id"], name: "index_trips_on_user_id"
|
||||
end
|
||||
|
||||
create_table "user_digests", force: :cascade do |t|
|
||||
t.bigint "user_id", null: false
|
||||
t.integer "kind", default: 0, null: false
|
||||
t.datetime "start_at", null: false
|
||||
t.datetime "end_at"
|
||||
t.integer "distance", default: 0, null: false
|
||||
t.text "countries", default: [], array: true
|
||||
t.text "cities", default: [], array: true
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["distance"], name: "index_user_digests_on_distance"
|
||||
t.index ["kind"], name: "index_user_digests_on_kind"
|
||||
t.index ["user_id"], name: "index_user_digests_on_user_id"
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
|
|
@ -216,6 +242,8 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_30_152025) do
|
|||
add_foreign_key "points", "users"
|
||||
add_foreign_key "points", "visits"
|
||||
add_foreign_key "stats", "users"
|
||||
add_foreign_key "trips", "users"
|
||||
add_foreign_key "user_digests", "users"
|
||||
add_foreign_key "visits", "areas"
|
||||
add_foreign_key "visits", "places"
|
||||
add_foreign_key "visits", "users"
|
||||
|
|
|
|||
9
spec/factories/trips.rb
Normal file
9
spec/factories/trips.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
FactoryBot.define do
|
||||
factory :trip do
|
||||
name { "MyString" }
|
||||
started_at { "2024-11-27 17:16:21" }
|
||||
ended_at { "2024-11-27 17:16:21" }
|
||||
notes { "MyText" }
|
||||
user { nil }
|
||||
end
|
||||
end
|
||||
15
spec/models/trip_spec.rb
Normal file
15
spec/models/trip_spec.rb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Trip, type: :model do
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_presence_of(:started_at) }
|
||||
it { is_expected.to validate_presence_of(:ended_at) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:user) }
|
||||
end
|
||||
end
|
||||
|
|
@ -13,6 +13,7 @@ RSpec.describe User, type: :model do
|
|||
it { is_expected.to have_many(:areas).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:visits).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:places).through(:visits) }
|
||||
it { is_expected.to have_many(:trips).dependent(:destroy) }
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
|
|
|
|||
131
spec/requests/trips_spec.rb
Normal file
131
spec/requests/trips_spec.rb
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
require 'rails_helper'
|
||||
|
||||
# This spec was generated by rspec-rails when you ran the scaffold generator.
|
||||
# It demonstrates how one might use RSpec to test the controller code that
|
||||
# was generated by Rails when you ran the scaffold generator.
|
||||
#
|
||||
# It assumes that the implementation code is generated by the rails scaffold
|
||||
# generator. If you are using any extension libraries to generate different
|
||||
# controller code, this generated spec may or may not pass.
|
||||
#
|
||||
# It only uses APIs available in rails and/or rspec-rails. There are a number
|
||||
# of tools you can use to make these specs even more expressive, but we're
|
||||
# sticking to rails and rspec-rails APIs to keep things simple and stable.
|
||||
|
||||
RSpec.describe "/trips", type: :request do
|
||||
|
||||
# This should return the minimal set of attributes required to create a valid
|
||||
# Trip. As you add validations to Trip, be sure to
|
||||
# adjust the attributes here as well.
|
||||
let(:valid_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
let(:invalid_attributes) {
|
||||
skip("Add a hash of attributes invalid for your model")
|
||||
}
|
||||
|
||||
describe "GET /index" do
|
||||
it "renders a successful response" do
|
||||
Trip.create! valid_attributes
|
||||
get trips_url
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /show" do
|
||||
it "renders a successful response" do
|
||||
trip = Trip.create! valid_attributes
|
||||
get trip_url(trip)
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /new" do
|
||||
it "renders a successful response" do
|
||||
get new_trip_url
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /edit" do
|
||||
it "renders a successful response" do
|
||||
trip = Trip.create! valid_attributes
|
||||
get edit_trip_url(trip)
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /create" do
|
||||
context "with valid parameters" do
|
||||
it "creates a new Trip" do
|
||||
expect {
|
||||
post trips_url, params: { trip: valid_attributes }
|
||||
}.to change(Trip, :count).by(1)
|
||||
end
|
||||
|
||||
it "redirects to the created trip" do
|
||||
post trips_url, params: { trip: valid_attributes }
|
||||
expect(response).to redirect_to(trip_url(Trip.last))
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid parameters" do
|
||||
it "does not create a new Trip" do
|
||||
expect {
|
||||
post trips_url, params: { trip: invalid_attributes }
|
||||
}.to change(Trip, :count).by(0)
|
||||
end
|
||||
|
||||
it "renders a response with 422 status (i.e. to display the 'new' template)" do
|
||||
post trips_url, params: { trip: invalid_attributes }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH /update" do
|
||||
context "with valid parameters" do
|
||||
let(:new_attributes) {
|
||||
skip("Add a hash of attributes valid for your model")
|
||||
}
|
||||
|
||||
it "updates the requested trip" do
|
||||
trip = Trip.create! valid_attributes
|
||||
patch trip_url(trip), params: { trip: new_attributes }
|
||||
trip.reload
|
||||
skip("Add assertions for updated state")
|
||||
end
|
||||
|
||||
it "redirects to the trip" do
|
||||
trip = Trip.create! valid_attributes
|
||||
patch trip_url(trip), params: { trip: new_attributes }
|
||||
trip.reload
|
||||
expect(response).to redirect_to(trip_url(trip))
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid parameters" do
|
||||
it "renders a response with 422 status (i.e. to display the 'edit' template)" do
|
||||
trip = Trip.create! valid_attributes
|
||||
patch trip_url(trip), params: { trip: invalid_attributes }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /destroy" do
|
||||
it "destroys the requested trip" do
|
||||
trip = Trip.create! valid_attributes
|
||||
expect {
|
||||
delete trip_url(trip)
|
||||
}.to change(Trip, :count).by(-1)
|
||||
end
|
||||
|
||||
it "redirects to the trips list" do
|
||||
trip = Trip.create! valid_attributes
|
||||
delete trip_url(trip)
|
||||
expect(response).to redirect_to(trips_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue