From 277825f9d187f1fc36280f3ae705fb0b7dd7ba16 Mon Sep 17 00:00:00 2001 From: Bogdanov Anton Date: Thu, 29 Feb 2024 14:35:27 +0300 Subject: [PATCH] added rendering/creating/editing Cups::Pair --- app/contracts/cups/pair_contract.rb | 14 ++ .../admin/cups/pairs_controller.rb | 82 ++++++++ .../admin/cups/rounds_controller.rb | 6 +- app/forms/cups/pairs/create_form.rb | 18 ++ app/forms/cups/pairs/update_form.rb | 18 ++ app/validators/cups/pair_validator.rb | 7 + .../admin/cups/pairs/_form.html.erb | 16 ++ .../admin/cups/pairs/edit.html.erb | 7 + .../admin/cups/pairs/index.html.erb | 30 +++ .../controllers/admin/cups/pairs/new.html.erb | 7 + .../admin/cups/rounds/index.html.erb | 2 +- config/initializers/container.rb | 4 + config/locales/en.yml | 23 +++ config/locales/ru.yml | 23 +++ config/routes.rb | 5 + .../admin/cups/pairs_controller_spec.rb | 188 ++++++++++++++++++ 16 files changed, 448 insertions(+), 2 deletions(-) create mode 100644 app/contracts/cups/pair_contract.rb create mode 100644 app/controllers/admin/cups/pairs_controller.rb create mode 100644 app/forms/cups/pairs/create_form.rb create mode 100644 app/forms/cups/pairs/update_form.rb create mode 100644 app/validators/cups/pair_validator.rb create mode 100644 app/views/controllers/admin/cups/pairs/_form.html.erb create mode 100644 app/views/controllers/admin/cups/pairs/edit.html.erb create mode 100644 app/views/controllers/admin/cups/pairs/index.html.erb create mode 100644 app/views/controllers/admin/cups/pairs/new.html.erb create mode 100644 spec/controllers/admin/cups/pairs_controller_spec.rb diff --git a/app/contracts/cups/pair_contract.rb b/app/contracts/cups/pair_contract.rb new file mode 100644 index 00000000..d2de1ee2 --- /dev/null +++ b/app/contracts/cups/pair_contract.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Cups + class PairContract < ApplicationContract + config.messages.namespace = :cups_pair + + params do + optional(:home_name).filled(:string) + optional(:visitor_name).filled(:string) + optional(:start_at) + optional(:points) + end + end +end diff --git a/app/controllers/admin/cups/pairs_controller.rb b/app/controllers/admin/cups/pairs_controller.rb new file mode 100644 index 00000000..f1204f88 --- /dev/null +++ b/app/controllers/admin/cups/pairs_controller.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module Admin + module Cups + class PairsController < Admin::BaseController + include Deps[ + create_form: 'forms.cups.pairs.create', + update_form: 'forms.cups.pairs.update' + ] + + before_action :find_cups_pairs, only: %i[index] + before_action :find_cups_round, only: %i[new edit create update] + before_action :find_cups_pair, only: %i[edit update] + + def index; end + + def new + @cups_pair = ::Cups::Pair.new + end + + def edit; end + + def create + case create_form.call(cups_round: @cups_round, params: cups_pair_params) + in { errors: errors } + redirect_to new_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id]), alert: errors + else + redirect_to( + admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]), + notice: t('controllers.admin.cups.pairs.create.success') + ) + end + end + + def update + case update_form.call(cups_pair: @cups_pair, params: cups_pair_params) + in { errors: errors } + redirect_to( + edit_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: @cups_pair.id), + alert: errors + ) + else + redirect_to( + admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]), + notice: t('controllers.admin.cups.pairs.update.success') + ) + end + end + + private + + def find_cups_pairs + @cups_pairs = + ::Cups::Pair + .where(cups_round_id: params[:cups_round_id]) + .order(start_at: :asc) + .hashable_pluck(:id, :home_name, :visitor_name, :start_at, :points) + end + + def find_cups_round + @cups_round = ::Cups::Round.find(params[:cups_round_id]) + end + + def find_cups_pair + @cups_pair = @cups_round.cups_pairs.find(params[:id]) + end + + # rubocop: disable Metrics/AbcSize + def cups_pair_params + params + .require(:cups_pair) + .permit(:home_name, :visitor_name) + .to_h + .merge( + start_at: params[:cups_pair][:start_at].present? ? DateTime.parse(params[:cups_pair][:start_at]) : nil, + points: params[:cups_pair][:points].present? ? params[:cups_pair][:points].split('-') : [] + ) + end + # rubocop: enable Metrics/AbcSize + end + end +end diff --git a/app/controllers/admin/cups/rounds_controller.rb b/app/controllers/admin/cups/rounds_controller.rb index 23202dd6..48a85642 100644 --- a/app/controllers/admin/cups/rounds_controller.rb +++ b/app/controllers/admin/cups/rounds_controller.rb @@ -27,7 +27,11 @@ def create private def find_cups_rounds - @cups_rounds = ::Cups::Round.where(cup_id: params[:cup_id]).order(position: :asc) + @cups_rounds = + ::Cups::Round + .where(cup_id: params[:cup_id]) + .order(position: :asc) + .hashable_pluck(:id, :name, :position) end def find_cup diff --git a/app/forms/cups/pairs/create_form.rb b/app/forms/cups/pairs/create_form.rb new file mode 100644 index 00000000..5c68ee0e --- /dev/null +++ b/app/forms/cups/pairs/create_form.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Cups + module Pairs + class CreateForm + include Deps[validator: 'validators.cups.pair'] + + def call(cups_round:, params:) + errors = validator.call(params: params) + return { errors: errors } if errors.any? + + result = cups_round.cups_pairs.create!(params) + + { result: result } + end + end + end +end diff --git a/app/forms/cups/pairs/update_form.rb b/app/forms/cups/pairs/update_form.rb new file mode 100644 index 00000000..75915fea --- /dev/null +++ b/app/forms/cups/pairs/update_form.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Cups + module Pairs + class UpdateForm + include Deps[validator: 'validators.cups.pair'] + + def call(cups_pair:, params:) + errors = validator.call(params: params) + return { errors: errors } if errors.any? + + cups_pair.update!(params) + + { result: cups_pair.reload } + end + end + end +end diff --git a/app/validators/cups/pair_validator.rb b/app/validators/cups/pair_validator.rb new file mode 100644 index 00000000..f6fba2da --- /dev/null +++ b/app/validators/cups/pair_validator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Cups + class PairValidator < ApplicationValidator + include Deps[contract: 'contracts.cups.pair'] + end +end diff --git a/app/views/controllers/admin/cups/pairs/_form.html.erb b/app/views/controllers/admin/cups/pairs/_form.html.erb new file mode 100644 index 00000000..55b28e23 --- /dev/null +++ b/app/views/controllers/admin/cups/pairs/_form.html.erb @@ -0,0 +1,16 @@ +
+ <%= form.label :home_name, t('views.admin.cups.pairs.index.home_name'), class: 'form-label' %> + <%= form.text_field :home_name, value: @cups_pair.home_name, class: 'form-value' %> +
+
+ <%= form.label :visitor_name, t('views.admin.cups.pairs.index.visitor_name'), class: 'form-label' %> + <%= form.text_field :visitor_name, value: @cups_pair.visitor_name, class: 'form-value' %> +
+
+ <%= form.label :start_at, t('views.admin.cups.pairs.index.start_at'), class: 'form-label' %> + <%= form.text_field :start_at, value: @cups_pair.start_at&.strftime('%Y-%m-%d %H:%M'), placeholder: '2024-01-31 12:13', class: 'form-value' %> +
+
+ <%= form.label :points, t('views.admin.cups.pairs.index.points'), class: 'form-label' %> + <%= form.text_field :points, value: @cups_pair.points&.join('-'), placeholder: '2-1', class: 'form-value' %> +
diff --git a/app/views/controllers/admin/cups/pairs/edit.html.erb b/app/views/controllers/admin/cups/pairs/edit.html.erb new file mode 100644 index 00000000..30e807d1 --- /dev/null +++ b/app/views/controllers/admin/cups/pairs/edit.html.erb @@ -0,0 +1,7 @@ +

<%= t('views.admin.cups.pairs.edit.title') %>

+
+ <%= form_with model: @cups_pair, url: admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: params[:id]), method: :patch do |form| %> + <%= render 'form', form: form %> + <%= form.submit t('views.admin.cups.pairs.edit.update'), class: 'btn-primary' %> + <% end %> +
diff --git a/app/views/controllers/admin/cups/pairs/index.html.erb b/app/views/controllers/admin/cups/pairs/index.html.erb new file mode 100644 index 00000000..f5015359 --- /dev/null +++ b/app/views/controllers/admin/cups/pairs/index.html.erb @@ -0,0 +1,30 @@ +
+

<%= t('views.admin.cups.pairs.index.header') %>

+ <%= link_to t('views.admin.cups.pairs.index.new_cups_pair'), new_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id]), class: 'btn-primary' %> +
+ + + + + + + + + + + + + <% @cups_pairs.each do |cups_pair| %> + + + + + + + + + <% end %> + +
ID<%= t('views.admin.cups.pairs.index.home_name') %><%= t('views.admin.cups.pairs.index.visitor_name') %><%= t('views.admin.cups.pairs.index.start_at') %><%= t('views.admin.cups.pairs.index.points') %>
<%= cups_pair[:id] %><%= cups_pair[:home_name] %><%= cups_pair[:visitor_name] %><%= cups_pair[:start_at] %><%= cups_pair[:points]&.join('-') %> + <%= link_to t('views.admin.cups.pairs.index.edit'), edit_admin_cups_round_pair_path(cups_round_id: params[:cups_round_id], id: cups_pair[:id]), class: 'btn-primary btn-small' %> +
diff --git a/app/views/controllers/admin/cups/pairs/new.html.erb b/app/views/controllers/admin/cups/pairs/new.html.erb new file mode 100644 index 00000000..d5e9ed92 --- /dev/null +++ b/app/views/controllers/admin/cups/pairs/new.html.erb @@ -0,0 +1,7 @@ +

<%= t('views.admin.cups.pairs.index.new_cups_pair') %>

+
+ <%= form_with model: @cups_pair, url: admin_cups_round_pairs_path(cups_round_id: params[:cups_round_id]), method: :post do |form| %> + <%= render 'form', form: form %> + <%= form.submit t('views.admin.cups.pairs.new.create'), class: 'btn-primary' %> + <% end %> +
diff --git a/app/views/controllers/admin/cups/rounds/index.html.erb b/app/views/controllers/admin/cups/rounds/index.html.erb index 62262b0a..b4ee0f2a 100644 --- a/app/views/controllers/admin/cups/rounds/index.html.erb +++ b/app/views/controllers/admin/cups/rounds/index.html.erb @@ -18,7 +18,7 @@ <%= cups_round[:name] %> <%= cups_round[:position] %> - + <%= link_to t('views.admin.cups.rounds.index.pairs'), admin_cups_round_pairs_path(cups_round_id: cups_round[:id]), class: 'btn-primary btn-small' %> <% end %> diff --git a/config/initializers/container.rb b/config/initializers/container.rb index 8a5a30f7..2ef31c3d 100644 --- a/config/initializers/container.rb +++ b/config/initializers/container.rb @@ -44,6 +44,7 @@ def register(key) register('contracts.oracul') { OraculContract.new } register('contracts.cup') { CupContract.new } register('contracts.cups.round') { Cups::RoundContract.new } + register('contracts.cups.pair') { Cups::PairContract.new } # validators register('validators.games.create') { Games::CreateValidator.new } @@ -68,6 +69,7 @@ def register(key) register('validators.oracul') { OraculValidator.new } register('validators.cup') { CupValidator.new } register('validators.cups.round') { Cups::RoundValidator.new } + register('validators.cups.pair') { Cups::PairValidator.new } # forms register('forms.teams.players.create') { Teams::Players::CreateForm.new } @@ -93,6 +95,8 @@ def register(key) register('forms.oraculs.forecasts.update') { Oraculs::Forecasts::UpdateForm.new } register('forms.cups.create') { Cups::CreateForm.new } register('forms.cups.rounds.create') { Cups::Rounds::CreateForm.new } + register('forms.cups.pairs.create') { Cups::Pairs::CreateForm.new } + register('forms.cups.pairs.update') { Cups::Pairs::UpdateForm.new } # notifiers register('notifiers.telegram.user.deadline_report_payload') { Telegram::User::DeadlineReportPayload.new } diff --git a/config/locales/en.yml b/config/locales/en.yml index e569cdd4..ab0805f5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -65,6 +65,11 @@ en: rounds: create: success: Cups round is created + pairs: + create: + success: Cups pair is created + update: + success: Cups pair is updated seasons: create: success: Season is created @@ -126,6 +131,9 @@ en: permission: Email is not confirmed yet ban: Account is banned services: + cups: + create: + league_does_not_exist: League does not exist oraculs: create: not_unique: Oracul already exists @@ -186,8 +194,23 @@ en: new_cups_round: New cups round name: Name position: Position + pairs: Games new: create: Create new cups round + pairs: + index: + header: Round games + new_cups_pair: New round game + home_name: Home name + visitor_name: Visitor name + start_at: Start at + points: Points + edit: Edit + new: + create: Create new pair + edit: + title: Editing pair + update: Update pair weeks: index: header: Weeks diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 33b4edcb..0c6c038f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -65,6 +65,11 @@ ru: rounds: create: success: Кубковый раунд создан + pairs: + create: + success: Кубковая игры создана + update: + success: Кубковая игры обновлена seasons: create: success: Сезон создан @@ -126,6 +131,9 @@ ru: permission: Необходимо подтвердить электронную почту ban: Аккаунт заблокирован services: + cups: + create: + league_does_not_exist: Лига не существует oraculs: create: not_unique: Оракл уже создан @@ -186,8 +194,23 @@ ru: new_cups_round: Новый кубковый раунд name: Название position: Положение + pairs: Игры new: create: Создать новый раунд + pairs: + index: + header: Игры раунда + new_cups_pair: Новая игры + home_name: Первое название + visitor_name: Второе название + start_at: Время начала + points: Результат + edit: Изменить + new: + create: Создать новую игру + edit: + title: Редактирование игры + update: Обновить игру weeks: index: header: Игровые недели diff --git a/config/routes.rb b/config/routes.rb index a26fe420..41ccbd3f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,6 +32,11 @@ resources :rounds, only: %i[index new create] end end + resources :cups_rounds, only: %i[] do + scope module: :cups do + resources :pairs, only: %i[index new edit create update] + end + end resources :weeks, only: %i[index edit update] end diff --git a/spec/controllers/admin/cups/pairs_controller_spec.rb b/spec/controllers/admin/cups/pairs_controller_spec.rb new file mode 100644 index 00000000..e88a88c3 --- /dev/null +++ b/spec/controllers/admin/cups/pairs_controller_spec.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +describe Admin::Cups::PairsController do + let!(:cup) { create :cup } + let!(:cups_round) { create :cups_round, cup: cup } + + describe 'GET#index' do + it_behaves_like 'required auth' + it_behaves_like 'required email confirmation' + it_behaves_like 'required available email' + it_behaves_like 'required admin' + + context 'for admin' do + sign_in_admin + + context 'for authorized user' do + before { create :cups_pair, cups_round: cups_round } + + it 'renders index page' do + do_request + + expect(response).to render_template :index + end + end + end + + def do_request + get :index, params: { cups_round_id: cups_round.id, locale: 'en' } + end + end + + describe 'GET#new' do + it_behaves_like 'required auth' + it_behaves_like 'required email confirmation' + it_behaves_like 'required available email' + it_behaves_like 'required admin' + + context 'for admin' do + sign_in_admin + + context 'for unexisting cups round' do + it 'render not found page' do + do_request + + expect(response).to render_template 'shared/404' + end + end + + context 'for existing cups round' do + it 'renders new page' do + get :new, params: { cups_round_id: cups_round.id, locale: 'en' } + + expect(response).to render_template :new + end + end + end + + def do_request + get :new, params: { cups_round_id: 'unexisting', locale: 'en' } + end + end + + describe 'GET#edit' do + it_behaves_like 'required auth' + it_behaves_like 'required email confirmation' + it_behaves_like 'required available email' + it_behaves_like 'required admin' + + context 'for admin' do + sign_in_admin + + context 'for unexisting cups round' do + it 'render not found page' do + do_request + + expect(response).to render_template 'shared/404' + end + end + + context 'for unexisting cups pair' do + it 'render not found page' do + get :edit, params: { cups_round_id: cups_round.id, id: 'unexisting', locale: 'en' } + + expect(response).to render_template 'shared/404' + end + end + + context 'for existing cups round and pair' do + let!(:cups_pair) { create :cups_pair, cups_round: cups_round } + + it 'renders edit page' do + get :edit, params: { cups_round_id: cups_round.id, id: cups_pair, locale: 'en' } + + expect(response).to render_template :edit + end + end + end + + def do_request + get :edit, params: { cups_round_id: 'unexisting', id: 'unexisting', locale: 'en' } + end + end + + describe 'POST#create' do + it_behaves_like 'required auth' + it_behaves_like 'required email confirmation' + it_behaves_like 'required available email' + it_behaves_like 'required admin' + + context 'for admin' do + sign_in_admin + + context 'for unexisting cups round' do + it 'does not create cups pair', :aggregate_failures do + expect { do_request }.not_to change(Cups::Pair, :count) + expect(response).to render_template 'shared/404' + end + end + + context 'for existing cups round' do + let(:request) { + post :create, params: { + cups_round_id: cups_round.id, cups_pair: { home_name: 'Home', visitor_name: 'Visitor' } + } + } + + it 'creates cups pair', :aggregate_failures do + expect { request }.to change(cups_round.cups_pairs, :count).by(1) + expect(response).to redirect_to admin_cups_round_pairs_path(cups_round_id: cups_round.id) + end + end + end + + def do_request + post :create, params: { cups_round_id: 'unexisting', cups_pair: { home_name: 'Home', visitor_name: 'Visitor' } } + end + end + + describe 'PATCH#update' do + it_behaves_like 'required auth' + it_behaves_like 'required email confirmation' + it_behaves_like 'required available email' + it_behaves_like 'required admin' + + context 'for admin' do + sign_in_admin + + context 'for unexisting cups round' do + it 'redirects to not found page' do + do_request + + expect(response).to render_template 'shared/404' + end + end + + context 'for unexisting cups pair' do + it 'redirects to not found page' do + patch :update, params: { cups_round_id: cups_round.id, id: 'unexisting', cups_pair: { home_name: 'Home' } } + + expect(response).to render_template 'shared/404' + end + end + + context 'for existing cups round' do + let!(:cups_pair) { create :cups_pair, cups_round: cups_round, home_name: 'Old home', points: [3, 1] } + let(:request) { + patch :update, params: { + cups_round_id: cups_round.id, id: cups_pair.id, cups_pair: { home_name: 'Home', points: '2-1' } + } + } + + it 'updates cups pair', :aggregate_failures do + request + + expect(cups_pair.reload.home_name).to eq 'Home' + expect(cups_pair.reload.points).to eq([2, 1]) + expect(response).to redirect_to admin_cups_round_pairs_path(cups_round_id: cups_round.id) + end + end + end + + def do_request + patch :update, params: { + cups_round_id: 'unexisting', id: 'unexisting', cups_pair: { home_name: 'Home', visitor_name: 'Visitor' } + } + end + end +end