From 7cd27d687f6341a510dfb158dfe7d2ac82409951 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Wed, 20 May 2026 17:36:24 +0900 Subject: [PATCH 1/9] =?UTF-8?q?product=5Ftemplate=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AB=E3=81=A8=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=A9=E3=81=AE=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/product_templates_controller.rb | 31 +++++++++++++++++++ app/models/practice.rb | 1 + app/models/product_template.rb | 6 ++++ ...20260520061059_create_product_templates.rb | 10 ++++++ db/schema.rb | 9 ++++++ 5 files changed, 57 insertions(+) create mode 100644 app/controllers/api/product_templates_controller.rb create mode 100644 app/models/product_template.rb create mode 100644 db/migrate/20260520061059_create_product_templates.rb diff --git a/app/controllers/api/product_templates_controller.rb b/app/controllers/api/product_templates_controller.rb new file mode 100644 index 00000000000..75c6c2d20a4 --- /dev/null +++ b/app/controllers/api/product_templates_controller.rb @@ -0,0 +1,31 @@ +class API::ProductTemplatesController < ApplicationController + before_action :set_product_template, only: %i[update] + + def create + @template = ReportTemplate.new(product_template_params) + @practice = Practice.find(params[:practice]) + if @template.save + render json: { id: @template.id }, status: :ok + else + head :bad_request + end + end + + def update + if @template.update(product_template_params) + head :ok + else + head :bad_request + end + end + + private + + def product_template_params + params.require(:product_template).permit(:description) + end + + def set_product_template + @template = Practice.find(params[:practice]).product_template + end +end diff --git a/app/models/practice.rb b/app/models/practice.rb index 412d5ed5ac7..4cc6a1d4576 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -25,6 +25,7 @@ class Practice < ApplicationRecord # rubocop:todo Metrics/ClassLength source: :user has_many :skipped_practices, dependent: :destroy has_many :products, dependent: :destroy + has_one :product_template, dependent: :destroy has_many :questions, dependent: :nullify has_many :pages, -> { order(updated_at: :desc, id: :desc) }, diff --git a/app/models/product_template.rb b/app/models/product_template.rb new file mode 100644 index 00000000000..163036778d2 --- /dev/null +++ b/app/models/product_template.rb @@ -0,0 +1,6 @@ +class ProductTemplate < ApplicationRecord + belongs_to :practice + + validates :description, presence: true + validates :practice, presence: true +end diff --git a/db/migrate/20260520061059_create_product_templates.rb b/db/migrate/20260520061059_create_product_templates.rb new file mode 100644 index 00000000000..870b364ebbd --- /dev/null +++ b/db/migrate/20260520061059_create_product_templates.rb @@ -0,0 +1,10 @@ +class CreateProductTemplates < ActiveRecord::Migration[8.1] + def change + create_table :product_templates do |t| + t.references :practice, null: false, foreign_key: true + t.text :description + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 99e52cbd229..ec3f6ea336c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -632,6 +632,14 @@ t.index ["report_id", "practice_id"], name: "index_practices_reports_on_report_id_and_practice_id" end + create_table "product_templates", force: :cascade do |t| + t.datetime "created_at", null: false + t.text "description" + t.bigint "practice_id", null: false + t.datetime "updated_at", null: false + t.index ["practice_id"], name: "index_product_templates_on_practice_id" + end + create_table "products", force: :cascade do |t| t.text "body" t.bigint "checker_id" @@ -1173,6 +1181,7 @@ add_foreign_key "practices_books", "practices" add_foreign_key "practices_movies", "movies" add_foreign_key "practices_movies", "practices" + add_foreign_key "product_templates", "practices" add_foreign_key "products", "practices" add_foreign_key "products", "users" add_foreign_key "questions", "practices" From 8034a2d0b2fcbb0f9370ddb79e503cd04a6a3ab6 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Wed, 20 May 2026 17:43:47 +0900 Subject: [PATCH 2/9] =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/routes/api.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes/api.rb b/config/routes/api.rb index 1b5e8e1d6b2..cf1d2e80324 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -85,6 +85,7 @@ resources :comments, only: %i(create), controller: 'products/comments' resource :check, only: %i(create destroy), controller: 'products/check' end + resources :product_templates, only: %i(create update) resources :searchables, only: %i(index) resources :bookmarks, only: %i(index create destroy) resources :report_templates, only: %i(create update) From 95cb0bf85abc2df0b4e18962452e7c6044cd6444 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Thu, 21 May 2026 16:31:41 +0900 Subject: [PATCH 3/9] =?UTF-8?q?product=20template=20view=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/api/product_templates/create.html.slim | 2 ++ app/views/api/product_templates/update.html.slim | 2 ++ app/views/products/_product_template.html.slim | 0 3 files changed, 4 insertions(+) create mode 100644 app/views/api/product_templates/create.html.slim create mode 100644 app/views/api/product_templates/update.html.slim create mode 100644 app/views/products/_product_template.html.slim diff --git a/app/views/api/product_templates/create.html.slim b/app/views/api/product_templates/create.html.slim new file mode 100644 index 00000000000..879b597592a --- /dev/null +++ b/app/views/api/product_templates/create.html.slim @@ -0,0 +1,2 @@ +h1 API::ProductTemplates#create +p Find me in app/views/api/product_templates/create.html.slim diff --git a/app/views/api/product_templates/update.html.slim b/app/views/api/product_templates/update.html.slim new file mode 100644 index 00000000000..6e2b44be1b2 --- /dev/null +++ b/app/views/api/product_templates/update.html.slim @@ -0,0 +1,2 @@ +h1 API::ProductTemplates#update +p Find me in app/views/api/product_templates/update.html.slim diff --git a/app/views/products/_product_template.html.slim b/app/views/products/_product_template.html.slim new file mode 100644 index 00000000000..e69de29bb2d From 535b264696d6f640732b2adcabec5b09d9666d84 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Fri, 22 May 2026 14:24:35 +0900 Subject: [PATCH 4/9] =?UTF-8?q?=E3=83=97=E3=83=A9=E3=82=AF=E3=83=86?= =?UTF-8?q?=E3=82=A3=E3=82=B9=E3=81=AE=E7=99=BB=E9=8C=B2=E3=83=BB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E7=94=BB=E9=9D=A2=E3=81=A7=E6=8F=90=E5=87=BA=E7=89=A9?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=83=B3=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=92=E7=99=BB=E9=8C=B2=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/product_templates_controller.rb | 31 ------------------- .../mentor/practices_controller.rb | 8 +++-- app/models/practice.rb | 3 ++ .../api/product_templates/create.html.slim | 2 -- .../api/product_templates/update.html.slim | 2 -- app/views/mentor/practices/_form.html.slim | 13 ++++++++ app/views/products/_form.html.slim | 4 ++- config/locales/ja.yml | 3 ++ config/routes/api.rb | 1 - db/schema.rb | 4 +-- 10 files changed, 30 insertions(+), 41 deletions(-) delete mode 100644 app/controllers/api/product_templates_controller.rb delete mode 100644 app/views/api/product_templates/create.html.slim delete mode 100644 app/views/api/product_templates/update.html.slim diff --git a/app/controllers/api/product_templates_controller.rb b/app/controllers/api/product_templates_controller.rb deleted file mode 100644 index 75c6c2d20a4..00000000000 --- a/app/controllers/api/product_templates_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -class API::ProductTemplatesController < ApplicationController - before_action :set_product_template, only: %i[update] - - def create - @template = ReportTemplate.new(product_template_params) - @practice = Practice.find(params[:practice]) - if @template.save - render json: { id: @template.id }, status: :ok - else - head :bad_request - end - end - - def update - if @template.update(product_template_params) - head :ok - else - head :bad_request - end - end - - private - - def product_template_params - params.require(:product_template).permit(:description) - end - - def set_product_template - @template = Practice.find(params[:practice]).product_template - end -end diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index 504b4014f94..16da4897979 100644 --- a/app/controllers/mentor/practices_controller.rb +++ b/app/controllers/mentor/practices_controller.rb @@ -12,9 +12,12 @@ def index def new @practice = Practice.new(pjord_review: true, pjord_auto_check: false) + @practice.build_product_template end - def edit; end + def edit + @practice.build_product_template unless @practice.product_template + end def create @practice = Practice.new(practice_params) @@ -63,7 +66,8 @@ def practice_params :summary, :ogp_image, category_ids: [], - practices_books_attributes: %i[id book_id must_read _destroy] + practices_books_attributes: %i[id book_id must_read _destroy], + product_template_attributes: %i[id description] ) end diff --git a/app/models/practice.rb b/app/models/practice.rb index 4cc6a1d4576..d187f7ea539 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -25,7 +25,10 @@ class Practice < ApplicationRecord # rubocop:todo Metrics/ClassLength source: :user has_many :skipped_practices, dependent: :destroy has_many :products, dependent: :destroy + has_one :product_template, dependent: :destroy + accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true + has_many :questions, dependent: :nullify has_many :pages, -> { order(updated_at: :desc, id: :desc) }, diff --git a/app/views/api/product_templates/create.html.slim b/app/views/api/product_templates/create.html.slim deleted file mode 100644 index 879b597592a..00000000000 --- a/app/views/api/product_templates/create.html.slim +++ /dev/null @@ -1,2 +0,0 @@ -h1 API::ProductTemplates#create -p Find me in app/views/api/product_templates/create.html.slim diff --git a/app/views/api/product_templates/update.html.slim b/app/views/api/product_templates/update.html.slim deleted file mode 100644 index 6e2b44be1b2..00000000000 --- a/app/views/api/product_templates/update.html.slim +++ /dev/null @@ -1,2 +0,0 @@ -h1 API::ProductTemplates#update -p Find me in app/views/api/product_templates/update.html.slim diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index 9e2869249c6..f8e82ca260b 100644 --- a/app/views/mentor/practices/_form.html.slim +++ b/app/views/mentor/practices/_form.html.slim @@ -109,6 +109,19 @@ = f.check_box :include_progress, class: 'a-toggle-checkbox' = f.label :include_progress | 進捗の計算に含むようにする場合はチェック + + = f.fields_for :product_template do |pt| + .form-item + .row.js-markdown-parent + .col-md-6.col-xs-12 + = pt.label :description, '提出物のテンプレート', class: 'a-form-label' + = pt.text_area :description, + class: 'a-text-input js-warning-form markdown-form__text-area js-markdown', + data: { 'preview': '.js-product-template-preview' } + .col-md-6.col-xs-12 + .a-form-label プレビュー + .js-product-template-preview.a-long-text.is-md.markdown-form__preview + .form-item .row.js-markdown-parent .col-md-6.col-xs-12 diff --git a/app/views/products/_form.html.slim b/app/views/products/_form.html.slim index 821fbcd2132..b70ce74da30 100644 --- a/app/views/products/_form.html.slim +++ b/app/views/products/_form.html.slim @@ -9,8 +9,10 @@ | 提出物 .form-textarea .form-textarea__body + - template = @product.practice.product_template&.description = f.text_area :body, class: 'a-text-input js-warning-form js-markdown markdown-form__text-area', - data: { 'preview': '.js-preview', 'input': '.file-input' } + data: { 'preview': '.js-preview', 'input': '.file-input' }, + value: @product.body.presence || template .form-textarea__footer .form-textarea__insert label.a-file-insert.a-button.is-xs.is-text-reversal.is-block diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 7e9b8bd0dc1..b5f2683cdd0 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -56,6 +56,7 @@ ja: answer: 回答 correct_answer: 模範回答 movie: 動画 + product_template: 提出物テンプレート search_labels: practice: "プラク\nティス" event: "特別\nイベント" @@ -183,6 +184,8 @@ ja: user: ユーザー practice: プラクティス body: 本文 + product_template: + description: 提出物のテンプレート submission_answer: description: 内容 company: diff --git a/config/routes/api.rb b/config/routes/api.rb index cf1d2e80324..1b5e8e1d6b2 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -85,7 +85,6 @@ resources :comments, only: %i(create), controller: 'products/comments' resource :check, only: %i(create destroy), controller: 'products/check' end - resources :product_templates, only: %i(create update) resources :searchables, only: %i(index) resources :bookmarks, only: %i(index create destroy) resources :report_templates, only: %i(create update) diff --git a/db/schema.rb b/db/schema.rb index ec3f6ea336c..a44c09e95d0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -634,10 +634,10 @@ create_table "product_templates", force: :cascade do |t| t.datetime "created_at", null: false - t.text "description" + t.text "description", null: false t.bigint "practice_id", null: false t.datetime "updated_at", null: false - t.index ["practice_id"], name: "index_product_templates_on_practice_id" + t.index ["practice_id"], name: "index_product_templates_on_practice_id", unique: true end create_table "products", force: :cascade do |t| From 5372b05ff3cde183a91814da9b42a570e139e69e Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Fri, 22 May 2026 16:56:24 +0900 Subject: [PATCH 5/9] =?UTF-8?q?=E6=8F=90=E5=87=BA=E7=89=A9=E3=83=86?= =?UTF-8?q?=E3=83=B3=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/practice.rb | 2 +- app/models/product_template.rb | 2 + .../products/_product_template.html.slim | 0 test/fixtures/product_templates.yml | 3 ++ test/system/practices/mentor_test.rb | 47 +++++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) delete mode 100644 app/views/products/_product_template.html.slim create mode 100644 test/fixtures/product_templates.yml diff --git a/app/models/practice.rb b/app/models/practice.rb index d187f7ea539..7e0f1ae6579 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -27,7 +27,7 @@ class Practice < ApplicationRecord # rubocop:todo Metrics/ClassLength has_many :products, dependent: :destroy has_one :product_template, dependent: :destroy - accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true + accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true, reject_if: proc { |attributes| attributes['description'].blank? } has_many :questions, dependent: :nullify has_many :pages, diff --git a/app/models/product_template.rb b/app/models/product_template.rb index 163036778d2..09b8bc805c7 100644 --- a/app/models/product_template.rb +++ b/app/models/product_template.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class ProductTemplate < ApplicationRecord belongs_to :practice diff --git a/app/views/products/_product_template.html.slim b/app/views/products/_product_template.html.slim deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/fixtures/product_templates.yml b/test/fixtures/product_templates.yml new file mode 100644 index 00000000000..30e4110715e --- /dev/null +++ b/test/fixtures/product_templates.yml @@ -0,0 +1,3 @@ +product_template1: + practice: practice1 + description: 更新前テンプレート diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 823668207d5..6b91f8dc973 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -21,6 +21,7 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' + fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' uncheck 'practice[pjord_review]', allow_label_click: true click_button '登録する' end @@ -28,6 +29,35 @@ class MentorTest < ApplicationSystemTestCase assert_not_predicate Practice.order(:created_at).last, :pjord_review? end + test 'can create practice without product template' do + visit_with_auth '/mentor/practices/new', 'komagata' + + within 'form[name=practice]' do + fill_in 'practice[title]', with: 'テンプレなし' + check categories(:category1).name, allow_label_click: true + fill_in 'practice[description]', with: 'テストの内容です' + fill_in 'practice[goal]', with: 'テストのゴールの内容です' + click_button '登録する' + end + + assert_text 'プラクティスを作成しました' + assert_not_predicate Practice.order(:created_at).last, :pjord_review? + end + + test 'can create practice without product template' do + visit_with_auth '/mentor/practices/new', 'komagata' + + within 'form[name=practice]' do + fill_in 'practice[title]', with: 'テンプレなし' + check categories(:category1).name, allow_label_click: true + fill_in 'practice[description]', with: 'テストの内容です' + fill_in 'practice[goal]', with: 'テストのゴールの内容です' + click_button '登録する' + end + + assert_text 'プラクティスを作成しました' + end + test 'create practice as a mentor' do visit_with_auth '/mentor/practices/new', 'mentormentaro' within 'form[name=practice]' do @@ -39,6 +69,7 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' + fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' click_button '登録する' end assert_text 'プラクティスを作成しました' @@ -51,6 +82,7 @@ class MentorTest < ApplicationSystemTestCase within 'form[name=practice]' do fill_in 'practice[title]', with: 'テストプラクティス' fill_in 'practice[memo]', with: 'メンター向けのメモの内容です' + fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' within '#reference_books' do click_link '書籍を選択' end @@ -60,6 +92,21 @@ class MentorTest < ApplicationSystemTestCase visit "/products/#{product.id}" check 'toggle-mentor-memo-body', allow_label_click: true, visible: false assert_text 'メンター向けのメモの内容です' + + visit new_product_path(practice_id: practice.id) + assert_field 'product[body]', with: 'テストテンプレート' + end + + test 'update product template' do + practice = practices(:practice1) + visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' + assert_field 'practice_product_template_attributes_description', with: '更新前テンプレート' + fill_in 'practice_product_template_attributes_description', with: '更新後テンプレート' + click_button '更新する' + assert_text 'プラクティスを更新しました' + + visit new_product_path(practice_id: practice.id) + assert_field 'product[body]', with: '更新後テンプレート' end test 'add a book' do From fee370fbd780e9db78e24c69df2c8ed9e405d3f2 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Tue, 26 May 2026 10:32:23 +0900 Subject: [PATCH 6/9] =?UTF-8?q?CodeRabbit=E6=8C=87=E6=91=98=E4=BA=8B?= =?UTF-8?q?=E9=A0=85=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/mentor/practices_controller.rb | 2 +- app/models/product_template.rb | 1 - .../20260520061059_create_product_templates.rb | 2 +- test/system/practices/mentor_test.rb | 17 +++-------------- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index 16da4897979..4ef61f7abe7 100644 --- a/app/controllers/mentor/practices_controller.rb +++ b/app/controllers/mentor/practices_controller.rb @@ -67,7 +67,7 @@ def practice_params :ogp_image, category_ids: [], practices_books_attributes: %i[id book_id must_read _destroy], - product_template_attributes: %i[id description] + product_template_attributes: %i[id description _destroy] ) end diff --git a/app/models/product_template.rb b/app/models/product_template.rb index 09b8bc805c7..eed70c79d72 100644 --- a/app/models/product_template.rb +++ b/app/models/product_template.rb @@ -4,5 +4,4 @@ class ProductTemplate < ApplicationRecord belongs_to :practice validates :description, presence: true - validates :practice, presence: true end diff --git a/db/migrate/20260520061059_create_product_templates.rb b/db/migrate/20260520061059_create_product_templates.rb index 870b364ebbd..0f18a84532a 100644 --- a/db/migrate/20260520061059_create_product_templates.rb +++ b/db/migrate/20260520061059_create_product_templates.rb @@ -1,7 +1,7 @@ class CreateProductTemplates < ActiveRecord::Migration[8.1] def change create_table :product_templates do |t| - t.references :practice, null: false, foreign_key: true + t.references :practice, null: false, foreign_key: true, index: { unique: true } t.text :description t.timestamps diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 6b91f8dc973..0db4b823114 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -41,21 +41,10 @@ class MentorTest < ApplicationSystemTestCase end assert_text 'プラクティスを作成しました' - assert_not_predicate Practice.order(:created_at).last, :pjord_review? - end - - test 'can create practice without product template' do - visit_with_auth '/mentor/practices/new', 'komagata' - within 'form[name=practice]' do - fill_in 'practice[title]', with: 'テンプレなし' - check categories(:category1).name, allow_label_click: true - fill_in 'practice[description]', with: 'テストの内容です' - fill_in 'practice[goal]', with: 'テストのゴールの内容です' - click_button '登録する' - end - - assert_text 'プラクティスを作成しました' + practice = Practice.find_by!(title: 'テンプレなし') + visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' + assert_empty find_field('practice_product_template_attributes_description').value end test 'create practice as a mentor' do From f4c93ee8a38d25166dc09eee1978742134c1b7cd Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Mon, 6 Jul 2026 10:59:56 +0900 Subject: [PATCH 7/9] =?UTF-8?q?ProductTemplate=E3=81=AEdescription?= =?UTF-8?q?=E3=81=ABNOT=20NULL=E5=88=B6=E7=B4=84=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/migrate/20260520061059_create_product_templates.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrate/20260520061059_create_product_templates.rb b/db/migrate/20260520061059_create_product_templates.rb index 0f18a84532a..8f2842606a3 100644 --- a/db/migrate/20260520061059_create_product_templates.rb +++ b/db/migrate/20260520061059_create_product_templates.rb @@ -2,7 +2,7 @@ class CreateProductTemplates < ActiveRecord::Migration[8.1] def change create_table :product_templates do |t| t.references :practice, null: false, foreign_key: true, index: { unique: true } - t.text :description + t.text :description, null: false t.timestamps end From 02b39ca5033430d61b314b1007ae1b9623e851f4 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Tue, 7 Jul 2026 10:39:10 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=E6=8F=90=E5=87=BA=E7=89=A9=E3=83=86?= =?UTF-8?q?=E3=83=B3=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E3=81=AE=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E6=A9=9F=E8=83=BD=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/practice.rb | 5 +++- app/views/mentor/practices/_form.html.slim | 15 +++++++--- test/fixtures/product_templates.yml | 2 +- test/models/practice_test.rb | 32 ++++++++++++++++++++++ test/system/practice/products_test.rb | 6 ++++ test/system/practices/mentor_test.rb | 15 +++++++++- 6 files changed, 68 insertions(+), 7 deletions(-) diff --git a/app/models/practice.rb b/app/models/practice.rb index 7e0f1ae6579..6c0a2c8731e 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -27,7 +27,10 @@ class Practice < ApplicationRecord # rubocop:todo Metrics/ClassLength has_many :products, dependent: :destroy has_one :product_template, dependent: :destroy - accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true, reject_if: proc { |attributes| attributes['description'].blank? } + accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true, + reject_if: proc { |attributes| + attributes['_destroy'] != '1' && attributes['description'].blank? + } has_many :questions, dependent: :nullify has_many :pages, diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index f8e82ca260b..dc98cc79ade 100644 --- a/app/views/mentor/practices/_form.html.slim +++ b/app/views/mentor/practices/_form.html.slim @@ -110,16 +110,23 @@ = f.label :include_progress | 進捗の計算に含むようにする場合はチェック - = f.fields_for :product_template do |pt| - .form-item + .form-item + label.a-form-label + | 提出物のテンプレート + = f.fields_for :product_template do |pt| + - if pt.object.persisted? + .checkboxes + ul.checkboxes__items + li.checkboxes__item + = pt.check_box :_destroy, class: 'a-toggle-checkbox' + = pt.label :_destroy do + | 提出物のテンプレートを削除する .row.js-markdown-parent .col-md-6.col-xs-12 - = pt.label :description, '提出物のテンプレート', class: 'a-form-label' = pt.text_area :description, class: 'a-text-input js-warning-form markdown-form__text-area js-markdown', data: { 'preview': '.js-product-template-preview' } .col-md-6.col-xs-12 - .a-form-label プレビュー .js-product-template-preview.a-long-text.is-md.markdown-form__preview .form-item diff --git a/test/fixtures/product_templates.yml b/test/fixtures/product_templates.yml index 30e4110715e..fda6c8ee755 100644 --- a/test/fixtures/product_templates.yml +++ b/test/fixtures/product_templates.yml @@ -1,3 +1,3 @@ product_template1: practice: practice1 - description: 更新前テンプレート + description: 確認用テンプレート diff --git a/test/models/practice_test.rb b/test/models/practice_test.rb index a1a63c97d47..8cae194ea81 100644 --- a/test/models/practice_test.rb +++ b/test/models/practice_test.rb @@ -148,4 +148,36 @@ class PracticeTest < ActiveSupport::TestCase assert_equal initial_count + 1, practice.reports_count(include_source: false) assert_equal initial_count_including_source + 1, practice.reports_count(include_source: true) end + + test 'does not create product template when description is blank' do + practice = practices(:practice1) + practice.product_template&.destroy! + + assert_no_difference 'ProductTemplate.count' do + practice.update!( + product_template_attributes: { + description: '' + } + ) + end + + assert_nil practice.reload.product_template + end + + test 'destroys product template with nested attributes' do + practice = practices(:practice1) + practice.product_template&.destroy! + product_template = practice.create_product_template!(description: '確認用テンプレート') + + assert_difference 'ProductTemplate.count', -1 do + practice.update!( + product_template_attributes: { + id: product_template.id, + _destroy: '1' + } + ) + end + + assert_nil practice.reload.product_template + end end diff --git a/test/system/practice/products_test.rb b/test/system/practice/products_test.rb index e620b0806ae..d50bf6d0d65 100644 --- a/test/system/practice/products_test.rb +++ b/test/system/practice/products_test.rb @@ -7,4 +7,10 @@ class Practice::ProductsTest < ApplicationSystemTestCase visit_with_auth "/practices/#{practices(:practice1).id}/products", 'komagata' assert_equal 'OS X Mountain Lionをクリーンインストールするの提出物 | FBC', title end + + test 'shows product template when product template exists' do + practice = practices(:practice1) + visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' + assert_field 'practice_product_template_attributes_description', with: '確認用テンプレート' + end end diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 0db4b823114..1a31222c4aa 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -89,7 +89,7 @@ class MentorTest < ApplicationSystemTestCase test 'update product template' do practice = practices(:practice1) visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' - assert_field 'practice_product_template_attributes_description', with: '更新前テンプレート' + assert_field 'practice_product_template_attributes_description', with: '確認用テンプレート' fill_in 'practice_product_template_attributes_description', with: '更新後テンプレート' click_button '更新する' assert_text 'プラクティスを更新しました' @@ -98,6 +98,19 @@ class MentorTest < ApplicationSystemTestCase assert_field 'product[body]', with: '更新後テンプレート' end + test 'deletes product template from practice edit form' do + practice = practices(:practice1) + visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' + check '提出物のテンプレートを削除する', allow_label_click: true + + assert_difference 'ProductTemplate.count', -1 do + click_button '更新する' + end + + assert_text 'プラクティスを更新しました' + assert_nil practice.reload.product_template + end + test 'add a book' do practice = practices(:practice2) book = books(:book1) From 43740965dfce118df9b1cbfac2dd20148e104d93 Mon Sep 17 00:00:00 2001 From: koguchi-e Date: Wed, 15 Jul 2026 17:29:35 +0900 Subject: [PATCH 9/9] =?UTF-8?q?product=5Ftemplate=E3=81=8B=E3=82=89templat?= =?UTF-8?q?e=E3=81=AB=E3=81=97=E3=80=81=E3=83=9D=E3=83=AA=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=95=E3=82=A3=E3=83=83=E3=82=AF=E9=96=A2=E9=80=A3?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/practices_controller.rb | 6 +++--- app/models/practice.rb | 10 +++++----- app/models/product_template.rb | 7 ------- app/models/template.rb | 7 +++++++ app/views/mentor/practices/_form.html.slim | 10 +++++----- app/views/products/_form.html.slim | 2 +- config/locales/ja.yml | 3 --- ...20260520061059_create_product_templates.rb | 10 ---------- db/migrate/20260520061059_create_templates.rb | 10 ++++++++++ db/schema.rb | 18 ++++++++--------- test/fixtures/product_templates.yml | 3 --- test/fixtures/templates.yml | 3 +++ test/models/practice_test.rb | 20 +++++++++---------- test/system/practice/products_test.rb | 2 +- test/system/practices/mentor_test.rb | 16 +++++++-------- 15 files changed, 62 insertions(+), 65 deletions(-) delete mode 100644 app/models/product_template.rb create mode 100644 app/models/template.rb delete mode 100644 db/migrate/20260520061059_create_product_templates.rb create mode 100644 db/migrate/20260520061059_create_templates.rb delete mode 100644 test/fixtures/product_templates.yml create mode 100644 test/fixtures/templates.yml diff --git a/app/controllers/mentor/practices_controller.rb b/app/controllers/mentor/practices_controller.rb index 4ef61f7abe7..a1d548eae6f 100644 --- a/app/controllers/mentor/practices_controller.rb +++ b/app/controllers/mentor/practices_controller.rb @@ -12,11 +12,11 @@ def index def new @practice = Practice.new(pjord_review: true, pjord_auto_check: false) - @practice.build_product_template + @practice.build_template end def edit - @practice.build_product_template unless @practice.product_template + @practice.build_template unless @practice.template end def create @@ -67,7 +67,7 @@ def practice_params :ogp_image, category_ids: [], practices_books_attributes: %i[id book_id must_read _destroy], - product_template_attributes: %i[id description _destroy] + template_attributes: %i[id description _destroy] ) end diff --git a/app/models/practice.rb b/app/models/practice.rb index 6c0a2c8731e..5c56075afc0 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -26,11 +26,11 @@ class Practice < ApplicationRecord # rubocop:todo Metrics/ClassLength has_many :skipped_practices, dependent: :destroy has_many :products, dependent: :destroy - has_one :product_template, dependent: :destroy - accepts_nested_attributes_for :product_template, update_only: true, allow_destroy: true, - reject_if: proc { |attributes| - attributes['_destroy'] != '1' && attributes['description'].blank? - } + has_one :template, as: :templatable, dependent: :destroy + accepts_nested_attributes_for :template, update_only: true, allow_destroy: true, + reject_if: proc { |attributes| + attributes['_destroy'] != '1' && attributes['description'].blank? + } has_many :questions, dependent: :nullify has_many :pages, diff --git a/app/models/product_template.rb b/app/models/product_template.rb deleted file mode 100644 index eed70c79d72..00000000000 --- a/app/models/product_template.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class ProductTemplate < ApplicationRecord - belongs_to :practice - - validates :description, presence: true -end diff --git a/app/models/template.rb b/app/models/template.rb new file mode 100644 index 00000000000..c9d19efd539 --- /dev/null +++ b/app/models/template.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Template < ApplicationRecord + belongs_to :templatable, polymorphic: true + + validates :description, presence: true +end diff --git a/app/views/mentor/practices/_form.html.slim b/app/views/mentor/practices/_form.html.slim index dc98cc79ade..ccf4aebd039 100644 --- a/app/views/mentor/practices/_form.html.slim +++ b/app/views/mentor/practices/_form.html.slim @@ -113,17 +113,17 @@ .form-item label.a-form-label | 提出物のテンプレート - = f.fields_for :product_template do |pt| - - if pt.object.persisted? + = f.fields_for :template do |template_form| + - if template_form.object.persisted? .checkboxes ul.checkboxes__items li.checkboxes__item - = pt.check_box :_destroy, class: 'a-toggle-checkbox' - = pt.label :_destroy do + = template_form.check_box :_destroy, class: 'a-toggle-checkbox' + = template_form.label :_destroy do | 提出物のテンプレートを削除する .row.js-markdown-parent .col-md-6.col-xs-12 - = pt.text_area :description, + = template_form.text_area :description, class: 'a-text-input js-warning-form markdown-form__text-area js-markdown', data: { 'preview': '.js-product-template-preview' } .col-md-6.col-xs-12 diff --git a/app/views/products/_form.html.slim b/app/views/products/_form.html.slim index b70ce74da30..6fd023fba13 100644 --- a/app/views/products/_form.html.slim +++ b/app/views/products/_form.html.slim @@ -9,7 +9,7 @@ | 提出物 .form-textarea .form-textarea__body - - template = @product.practice.product_template&.description + - template = @product.practice.template&.description = f.text_area :body, class: 'a-text-input js-warning-form js-markdown markdown-form__text-area', data: { 'preview': '.js-preview', 'input': '.file-input' }, value: @product.body.presence || template diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b5f2683cdd0..7e9b8bd0dc1 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -56,7 +56,6 @@ ja: answer: 回答 correct_answer: 模範回答 movie: 動画 - product_template: 提出物テンプレート search_labels: practice: "プラク\nティス" event: "特別\nイベント" @@ -184,8 +183,6 @@ ja: user: ユーザー practice: プラクティス body: 本文 - product_template: - description: 提出物のテンプレート submission_answer: description: 内容 company: diff --git a/db/migrate/20260520061059_create_product_templates.rb b/db/migrate/20260520061059_create_product_templates.rb deleted file mode 100644 index 8f2842606a3..00000000000 --- a/db/migrate/20260520061059_create_product_templates.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateProductTemplates < ActiveRecord::Migration[8.1] - def change - create_table :product_templates do |t| - t.references :practice, null: false, foreign_key: true, index: { unique: true } - t.text :description, null: false - - t.timestamps - end - end -end diff --git a/db/migrate/20260520061059_create_templates.rb b/db/migrate/20260520061059_create_templates.rb new file mode 100644 index 00000000000..5f08c8ab8bb --- /dev/null +++ b/db/migrate/20260520061059_create_templates.rb @@ -0,0 +1,10 @@ +class CreateTemplates < ActiveRecord::Migration[8.1] + def change + create_table :templates do |t| + t.references :templatable, polymorphic: true, null: false, index: { unique: true } + t.text :description, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a44c09e95d0..2ad96e6add0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -632,14 +632,6 @@ t.index ["report_id", "practice_id"], name: "index_practices_reports_on_report_id_and_practice_id" end - create_table "product_templates", force: :cascade do |t| - t.datetime "created_at", null: false - t.text "description", null: false - t.bigint "practice_id", null: false - t.datetime "updated_at", null: false - t.index ["practice_id"], name: "index_product_templates_on_practice_id", unique: true - end - create_table "products", force: :cascade do |t| t.text "body" t.bigint "checker_id" @@ -1033,6 +1025,15 @@ t.index ["user_id"], name: "index_talks_on_user_id" end + create_table "templates", force: :cascade do |t| + t.datetime "created_at", null: false + t.text "description", null: false + t.bigint "templatable_id", null: false + t.string "templatable_type", null: false + t.datetime "updated_at", null: false + t.index ["templatable_type", "templatable_id"], name: "index_templates_on_templatable", unique: true + end + create_table "users", id: :serial, force: :cascade do |t| t.datetime "accessed_at", precision: nil t.boolean "admin", default: false, null: false @@ -1181,7 +1182,6 @@ add_foreign_key "practices_books", "practices" add_foreign_key "practices_movies", "movies" add_foreign_key "practices_movies", "practices" - add_foreign_key "product_templates", "practices" add_foreign_key "products", "practices" add_foreign_key "products", "users" add_foreign_key "questions", "practices" diff --git a/test/fixtures/product_templates.yml b/test/fixtures/product_templates.yml deleted file mode 100644 index fda6c8ee755..00000000000 --- a/test/fixtures/product_templates.yml +++ /dev/null @@ -1,3 +0,0 @@ -product_template1: - practice: practice1 - description: 確認用テンプレート diff --git a/test/fixtures/templates.yml b/test/fixtures/templates.yml new file mode 100644 index 00000000000..fbb7cdfdeba --- /dev/null +++ b/test/fixtures/templates.yml @@ -0,0 +1,3 @@ +one: + templatable: practice1 (Practice) + description: 提出物のテンプレート diff --git a/test/models/practice_test.rb b/test/models/practice_test.rb index 8cae194ea81..183060fbf94 100644 --- a/test/models/practice_test.rb +++ b/test/models/practice_test.rb @@ -151,33 +151,33 @@ class PracticeTest < ActiveSupport::TestCase test 'does not create product template when description is blank' do practice = practices(:practice1) - practice.product_template&.destroy! + practice.template&.destroy! - assert_no_difference 'ProductTemplate.count' do + assert_no_difference 'Template.count' do practice.update!( - product_template_attributes: { + template_attributes: { description: '' } ) end - assert_nil practice.reload.product_template + assert_nil practice.reload.template end test 'destroys product template with nested attributes' do practice = practices(:practice1) - practice.product_template&.destroy! - product_template = practice.create_product_template!(description: '確認用テンプレート') + practice.template&.destroy! + template = practice.create_template!(description: '提出物のテンプレート') - assert_difference 'ProductTemplate.count', -1 do + assert_difference 'Template.count', -1 do practice.update!( - product_template_attributes: { - id: product_template.id, + template_attributes: { + id: template.id, _destroy: '1' } ) end - assert_nil practice.reload.product_template + assert_nil practice.reload.template end end diff --git a/test/system/practice/products_test.rb b/test/system/practice/products_test.rb index d50bf6d0d65..3599df705b1 100644 --- a/test/system/practice/products_test.rb +++ b/test/system/practice/products_test.rb @@ -11,6 +11,6 @@ class Practice::ProductsTest < ApplicationSystemTestCase test 'shows product template when product template exists' do practice = practices(:practice1) visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' - assert_field 'practice_product_template_attributes_description', with: '確認用テンプレート' + assert_field 'practice_template_attributes_description', with: '提出物のテンプレート' end end diff --git a/test/system/practices/mentor_test.rb b/test/system/practices/mentor_test.rb index 1a31222c4aa..0deeb064109 100644 --- a/test/system/practices/mentor_test.rb +++ b/test/system/practices/mentor_test.rb @@ -21,7 +21,7 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' - fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' + fill_in 'practice_template_attributes_description', with: 'テストテンプレート' uncheck 'practice[pjord_review]', allow_label_click: true click_button '登録する' end @@ -44,7 +44,7 @@ class MentorTest < ApplicationSystemTestCase practice = Practice.find_by!(title: 'テンプレなし') visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' - assert_empty find_field('practice_product_template_attributes_description').value + assert_empty find_field('practice_template_attributes_description').value end test 'create practice as a mentor' do @@ -58,7 +58,7 @@ class MentorTest < ApplicationSystemTestCase end fill_in 'practice[goal]', with: 'テストのゴールの内容です' fill_in 'practice[memo]', with: 'テストのメンター向けメモの内容です' - fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' + fill_in 'practice_template_attributes_description', with: 'テストテンプレート' click_button '登録する' end assert_text 'プラクティスを作成しました' @@ -71,7 +71,7 @@ class MentorTest < ApplicationSystemTestCase within 'form[name=practice]' do fill_in 'practice[title]', with: 'テストプラクティス' fill_in 'practice[memo]', with: 'メンター向けのメモの内容です' - fill_in 'practice_product_template_attributes_description', with: 'テストテンプレート' + fill_in 'practice_template_attributes_description', with: 'テストテンプレート' within '#reference_books' do click_link '書籍を選択' end @@ -89,8 +89,8 @@ class MentorTest < ApplicationSystemTestCase test 'update product template' do practice = practices(:practice1) visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' - assert_field 'practice_product_template_attributes_description', with: '確認用テンプレート' - fill_in 'practice_product_template_attributes_description', with: '更新後テンプレート' + assert_field 'practice_template_attributes_description', with: '提出物のテンプレート' + fill_in 'practice_template_attributes_description', with: '更新後テンプレート' click_button '更新する' assert_text 'プラクティスを更新しました' @@ -103,12 +103,12 @@ class MentorTest < ApplicationSystemTestCase visit_with_auth "/mentor/practices/#{practice.id}/edit", 'komagata' check '提出物のテンプレートを削除する', allow_label_click: true - assert_difference 'ProductTemplate.count', -1 do + assert_difference 'Template.count', -1 do click_button '更新する' end assert_text 'プラクティスを更新しました' - assert_nil practice.reload.product_template + assert_nil practice.reload.template end test 'add a book' do