Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabled editing the title of a collection via Stimulus #96

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion app/controllers/collections_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class CollectionsController < ApplicationController
include Filterable

load_resource only: %i[discard undiscard new_transition create_transition confirm_destroy]
load_and_authorize_resource only: %i[show new destroy]
load_and_authorize_resource only: %i[show new destroy update]

before_action :require_user, only: %i[index create_collection new_transition create_transition]
before_action :ensure_valid_config
Expand Down Expand Up @@ -54,6 +54,14 @@ def new
end
end

def update
if @collection.update(update_collection_params)
render json: { success: true }
else
render json: { success: false, errors: @collection.errors.full_messages }, status: :unprocessable_entity
end
end

# Create a new collection from an interpolation of a saved scenario
#
# GET /collections/new_transition
Expand Down Expand Up @@ -176,6 +184,10 @@ def ensure_valid_config
notice: 'Missing collections.uri setting in config.yml'
end

def update_collection_params
params.require(:collection).permit(:title)
end

def create_collection_params
params.require(:collection).permit(:title, :version, saved_scenario_ids: [])
end
Expand Down
50 changes: 25 additions & 25 deletions app/controllers/saved_scenarios_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -168,32 +168,32 @@ def undiscard

private

def user_saved_scenarios
current_user
.saved_scenarios
.available
.includes(:featured_scenario, :users)
.order("updated_at DESC")
end
def user_saved_scenarios
current_user
.saved_scenarios
.available
.includes(:featured_scenario, :users)
.order("updated_at DESC")
end

# Use callbacks to share common setup or constraints between actions.
def set_saved_scenario
@saved_scenario = SavedScenario.find(params[:id])
end
# Use callbacks to share common setup or constraints between actions.
def set_saved_scenario
@saved_scenario = SavedScenario.find(params[:id])
end

# Only allow a list of trusted parameters through.
def saved_scenario_params
params.require(:saved_scenario).permit(
:scenario_id, :scenario_id_history, :title,
:description, :area_code, :end_year, :private,
:created_at, :updated_at, :discarded_at
)
end
# Only allow a list of trusted parameters through.
def saved_scenario_params
params.require(:saved_scenario).permit(
:scenario_id, :scenario_id_history, :title,
:description, :area_code, :end_year, :private,
:created_at, :updated_at, :discarded_at
)
end

# Only allow a list of trusted parameters through.
def saved_scenario_update_params
params.require(:saved_scenario).permit(
:title, :description
)
end
# Only allow a list of trusted parameters through.
def saved_scenario_update_params
params.require(:saved_scenario).permit(
:title, :description
)
end
end
45 changes: 45 additions & 0 deletions app/javascript/controllers/editable_title_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static values = { url: String };

connect() {
this.originalText = this.element.innerText.trim();
this.element.setAttribute("spellcheck", "false");
}

async update(event) {
if (event.type === "keydown" && event.key === "Enter") {
event.preventDefault();
this.element.blur();
return;
}

const newTitle = this.element.innerText.trim();

if (newTitle === "" || newTitle === this.originalText) {
this.element.innerText = this.originalText;
return;
}

try {
const response = await fetch(this.urlValue, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": document.querySelector("[name='csrf-token']").content
},
body: JSON.stringify({ collection: { title: newTitle } })
});

if (!response.ok) {
throw new Error("Failed to update title");
}

this.originalText = newTitle;
} catch (error) {
alert("Error updating title");
this.element.innerText = this.originalText;
}
}
}
10 changes: 9 additions & 1 deletion app/views/collections/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<%- content_for :menu_title, @collection.title %>
<%- content_for :menu_title do %>
<span contenteditable="<%= can?(:update, @collection) %>"
class="relative <%= 'hover:underline hover:decoration-gray-400 hover:decoration-1 focus:outline-none' if can?(:update, @collection) %>"
data-controller="editable-title"
data-editable-title-url-value="<%= collection_path(@collection) %>"
data-action="<%= 'blur->editable-title#update keydown.enter->editable-title#update' if can?(:update, @collection) %>">
<%= @collection.title %>
</span>
<% end %>
<%- content_for :title, "#{@collection.title} - #{t('collections.title')} - #{t('meta.title')}" %>
<%= render(partial: "collections/show/block_right_menu", locals: { collection: @collection })%>

Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
end
end

resources :collections, only: %i[index show new destroy] do
resources :collections, only: %i[index show new destroy update] do
collection do
get :list
post :list
Expand Down
60 changes: 60 additions & 0 deletions spec/controllers/collections_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,64 @@
end
end
end

describe 'PUT update' do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
let(:collection) { create(:collection, title: 'Old title', user: user) }
let(:new_title) { 'New title' }

before do
sign_in user
end

context 'with a valid title' do
before do
put :update, params: { id: collection.id, collection: { title: new_title } }, format: :json
end

it 'updates the title of the collection' do
expect(collection.reload.title).to eq('New title')
end

it 'returns a success JSON response' do
expect(response).to have_http_status(:ok)
json_response = JSON.parse(response.body)
expect(json_response['success']).to be true
end
end

context 'with an invalid title' do
before do
put :update, params: { id: collection.id, collection: { title: '' } }, format: :json
end

it 'does not update the title of the collection' do
expect(collection.reload.title).to eq('Old title')
end

it 'returns an error JSON response' do
expect(response).to have_http_status(:unprocessable_entity)
json_response = JSON.parse(response.body)
expect(json_response['success']).to be false
expect(json_response['errors']).to include("Title can't be blank")
end
end

context 'when the user does not own the collection or have update permissions' do
before do
sign_out user
sign_in other_user
put :update, params: { id: collection.id, collection: { title: new_title } }, format: :json
end

it 'does not update the title of the collection' do
expect(collection.reload.title).to eq('Old title')
end

it 'cannot find the unowned collection' do
expect(response).to have_http_status(:not_found)
end
end
end
end
Loading