From e672ffe153f6453ac16dc128e36b49ce1628d466 Mon Sep 17 00:00:00 2001 From: Trevor Vallender Date: Mon, 17 Jun 2024 14:53:09 +0100 Subject: [PATCH] Refactor character sheet features --- app/controllers/counters_controller.rb | 5 +- app/controllers/stats_controller.rb | 5 +- app/controllers/text_fields_controller.rb | 4 ++ app/models/character_sheet_feature.rb | 49 +++++++++++++++++++ app/models/character_sheet_section.rb | 10 ++-- app/models/concerns/sluggable.rb | 36 -------------- app/models/counter.rb | 10 ++-- app/models/stat.rb | 11 ++--- app/models/text_field.rb | 4 +- app/views/counters/_form.html.erb | 7 ++- app/views/counters/new.turbo_stream.erb | 2 +- app/views/stats/_form.html.erb | 7 ++- app/views/stats/new.turbo_stream.erb | 2 +- app/views/text_fields/_form.html.erb | 7 ++- app/views/text_fields/new.turbo_stream.erb | 2 +- ...7112523_create_character_sheet_features.rb | 25 ++++++++++ db/schema.rb | 32 ++++++------ test/fixtures/character_sheet_features.yml | 20 ++++++++ test/fixtures/counters.yml | 2 - test/fixtures/stats.yml | 2 - test/fixtures/text_fields.yml | 1 - test/models/character_sheet_feature_test.rb | 9 ++++ test/models/stat_test.rb | 10 ++-- 23 files changed, 174 insertions(+), 88 deletions(-) create mode 100644 app/models/character_sheet_feature.rb delete mode 100644 app/models/concerns/sluggable.rb create mode 100644 db/migrate/20240617112523_create_character_sheet_features.rb create mode 100644 test/fixtures/character_sheet_features.yml create mode 100644 test/models/character_sheet_feature_test.rb diff --git a/app/controllers/counters_controller.rb b/app/controllers/counters_controller.rb index 239e9c8..975227a 100644 --- a/app/controllers/counters_controller.rb +++ b/app/controllers/counters_controller.rb @@ -7,6 +7,7 @@ class CountersController < ApplicationController def new @counter = @section.counters.new + @counter.build_character_sheet_feature end def create @@ -43,7 +44,9 @@ class CountersController < ApplicationController params.require(:counter).permit( :name, :value, - :character_sheet_section_id, + character_sheet_feature_attributes: [ + :id, :featurable_id, :featurable_type, :character_sheet_section_id, :_destroy, + ], ) end end diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index f08d448..24a8691 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -7,6 +7,7 @@ class StatsController < ApplicationController def new @stat = @section.stats.new + @stat.build_character_sheet_feature end def create @@ -45,7 +46,9 @@ class StatsController < ApplicationController :name, :value, :roll_command, - :character_sheet_section_id, + character_sheet_feature_attributes: [ + :id, :featurable_id, :featurable_type, :character_sheet_section_id, :_destroy, + ], ) end end diff --git a/app/controllers/text_fields_controller.rb b/app/controllers/text_fields_controller.rb index d56d024..60ff32a 100644 --- a/app/controllers/text_fields_controller.rb +++ b/app/controllers/text_fields_controller.rb @@ -7,6 +7,7 @@ class TextFieldsController < ApplicationController def new @text_field = @section.text_fields.new + @text_field.build_character_sheet_feature end def create @@ -44,6 +45,9 @@ class TextFieldsController < ApplicationController :name, :content, :character_sheet_section_id, + character_sheet_feature_attributes: [ + :id, :featurable_id, :featurable_type, :character_sheet_section_id, :_destroy, + ], ) end end diff --git a/app/models/character_sheet_feature.rb b/app/models/character_sheet_feature.rb new file mode 100644 index 0000000..d4de852 --- /dev/null +++ b/app/models/character_sheet_feature.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class CharacterSheetFeature < ApplicationRecord + belongs_to :character_sheet_section + belongs_to :featurable, polymorphic: true + + validates :order, presence: true, + numericality: { only_integer: true, greater_than: 0 } + + validates :slug, presence: true, + length: { maximum: 100 }, + uniqueness: { scope: :character_sheet_section_id } + + before_validation :set_slug + before_validation :set_order + + private + + def set_order + return if order.present? + + if character_sheet_section.character_sheet_features.any? + self.order = character_sheet_section.character_sheet_features.order(:order).last.order + 1 + else + self.order = 1 + end + end + + def set_slug + return if slug.present? || featurable.name.blank? + + slug = if character_sheet_section.parent_section.present? + [ + character_sheet_section.parent_section.name.parameterize, + featurable.name.parameterize, + ].join("-") + else + slug = featurable.name.parameterize + end + + suffix = 2 + while CharacterSheetFeature.exists?(slug:) + slug = "#{slug}-#{suffix}" + suffix += 1 + end + + self.slug = slug + end +end diff --git a/app/models/character_sheet_section.rb b/app/models/character_sheet_section.rb index 770a675..d7a6dc2 100644 --- a/app/models/character_sheet_section.rb +++ b/app/models/character_sheet_section.rb @@ -7,9 +7,13 @@ class CharacterSheetSection < ApplicationRecord has_many :character_sheet_subsections, class_name: "CharacterSheetSection", foreign_key: :parent_section_id, dependent: :destroy - has_many :counters, dependent: :destroy - has_many :stats, dependent: :destroy - has_many :text_fields, dependent: :destroy + has_many :character_sheet_features + has_many :counters, dependent: :destroy, through: :character_sheet_features, + source: :featurable, source_type: "Counter" + has_many :stats, dependent: :destroy, through: :character_sheet_features, + source: :featurable, source_type: "Stat" + has_many :text_fields, dependent: :destroy, through: :character_sheet_features, + source: :featurable, source_type: "TextField" validates :name, presence: true, length: { maximum: 255 } diff --git a/app/models/concerns/sluggable.rb b/app/models/concerns/sluggable.rb deleted file mode 100644 index 3f001ea..0000000 --- a/app/models/concerns/sluggable.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module Sluggable - extend ActiveSupport::Concern - - included do - validates :slug, presence: true, - length: { maximum: 100 }, - uniqueness: { scope: :character_sheet_section_id } - - before_validation :set_slug - - private - - def set_slug - return if slug.present? || name.blank? - - slug = if character_sheet_section.parent_section.present? - [ - character_sheet_section.parent_section.name.parameterize, - name.parameterize, - ].join("-") - else - slug = name.parameterize - end - - suffix = 2 - while Stat.exists?(slug:) - slug = "#{slug}-#{suffix}" - suffix += 1 - end - - self.slug = slug - end - end -end diff --git a/app/models/counter.rb b/app/models/counter.rb index be3c17e..109efe1 100644 --- a/app/models/counter.rb +++ b/app/models/counter.rb @@ -1,14 +1,12 @@ # frozen_string_literal: true class Counter < ApplicationRecord - include Sluggable - - belongs_to :character_sheet_section + has_one :character_sheet_feature, as: :featurable + has_one :character_sheet_section, through: :character_sheet_feature + accepts_nested_attributes_for :character_sheet_feature, allow_destroy: true validates :name, presence: true, - length: { maximum: 100 }, - uniqueness: { scope: :character_sheet_section_id } + length: { maximum: 100 } validates :value, presence: true, numericality: true - before_validation :set_slug end diff --git a/app/models/stat.rb b/app/models/stat.rb index c2e088b..7748a3d 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -1,18 +1,15 @@ # frozen_string_literal: true class Stat < ApplicationRecord - include Sluggable + has_one :character_sheet_feature, as: :featurable + has_one :character_sheet_section, through: :character_sheet_feature + accepts_nested_attributes_for :character_sheet_feature, allow_destroy: true - belongs_to :character_sheet_section has_one :character, through: :character_sheet_section has_many :dice_rolls, as: :rollable, dependent: :destroy validates :name, presence: true, - length: { maximum: 100 }, - uniqueness: { scope: :character_sheet_section_id } - validates :slug, presence: true, - length: { maximum: 100 }, - uniqueness: true + length: { maximum: 100 } validates :value, presence: true, numericality: true validate :validate_roll_command diff --git a/app/models/text_field.rb b/app/models/text_field.rb index d7444a7..c5b8efe 100644 --- a/app/models/text_field.rb +++ b/app/models/text_field.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true class TextField < ApplicationRecord - belongs_to :character_sheet_section + has_one :character_sheet_feature, as: :featurable + has_one :character_sheet_section, through: :character_sheet_feature + accepts_nested_attributes_for :character_sheet_feature, allow_destroy: true has_rich_text :content diff --git a/app/views/counters/_form.html.erb b/app/views/counters/_form.html.erb index ddd1fd2..49a113e 100644 --- a/app/views/counters/_form.html.erb +++ b/app/views/counters/_form.html.erb @@ -1,6 +1,11 @@
<%= form_with model: @counter, url: character_sheet_section_counters_path(@section) do |f| %> - <%= f.hidden_field :character_sheet_section_id, value: @section.id %> + <% if @counter.new_record? %> + <%= f.fields_for :character_sheet_feature do |ff| %> + <%= ff.hidden_field :featurable_id, value: @counter.id %> + <%= ff.hidden_field :character_sheet_section_id, value: @section.id %> + <% end %> + <% end %> <%= f.label :name %> <%= f.text_field :name %> diff --git a/app/views/counters/new.turbo_stream.erb b/app/views/counters/new.turbo_stream.erb index c56aa03..68fdf4d 100644 --- a/app/views/counters/new.turbo_stream.erb +++ b/app/views/counters/new.turbo_stream.erb @@ -1,4 +1,4 @@ -<% target = "#{dom_id(@counter.character_sheet_section)}_add_section" %> +<% target = "#{dom_id(@section)}_add_section" %> <%= turbo_stream.replace(target) do %>
<%= render partial: "form", locals: { button_text: t(".create_counter") } %> diff --git a/app/views/stats/_form.html.erb b/app/views/stats/_form.html.erb index fd5838a..1ee58e4 100644 --- a/app/views/stats/_form.html.erb +++ b/app/views/stats/_form.html.erb @@ -1,6 +1,11 @@
<%= form_with model: @stat, url: character_sheet_section_stats_path(@section) do |f| %> - <%= f.hidden_field :character_sheet_section_id, value: @section.id %> + <% if @stat.new_record? %> + <%= f.fields_for :character_sheet_feature do |ff| %> + <%= ff.hidden_field :featurable_id, value: @stat.id %> + <%= ff.hidden_field :character_sheet_section_id, value: @section.id %> + <% end %> + <% end %> <%= f.label :name %> <%= f.text_field :name %> diff --git a/app/views/stats/new.turbo_stream.erb b/app/views/stats/new.turbo_stream.erb index 5fc376d..a4f4616 100644 --- a/app/views/stats/new.turbo_stream.erb +++ b/app/views/stats/new.turbo_stream.erb @@ -1,4 +1,4 @@ -<% target = "#{dom_id(@stat.character_sheet_section)}_add_section" %> +<% target = "#{dom_id(@section)}_add_section" %> <%= turbo_stream.replace(target) do %>
<%= render partial: "form", locals: { button_text: t(".create_stat") } %> diff --git a/app/views/text_fields/_form.html.erb b/app/views/text_fields/_form.html.erb index 13f0bd4..15c6e50 100644 --- a/app/views/text_fields/_form.html.erb +++ b/app/views/text_fields/_form.html.erb @@ -1,6 +1,11 @@
<%= form_with model: @text_field, url: character_sheet_section_text_fields_path(@section) do |f| %> - <%= f.hidden_field :character_sheet_section_id, value: @section.id %> + <% if @text_field.new_record? %> + <%= f.fields_for :character_sheet_feature do |ff| %> + <%= ff.hidden_field :featurable_id, value: @text_field.id %> + <%= ff.hidden_field :character_sheet_section_id, value: @section.id %> + <% end %> + <% end %> <%= f.label :name %> <%= f.text_field :name %> diff --git a/app/views/text_fields/new.turbo_stream.erb b/app/views/text_fields/new.turbo_stream.erb index 26b1b29..576884e 100644 --- a/app/views/text_fields/new.turbo_stream.erb +++ b/app/views/text_fields/new.turbo_stream.erb @@ -1,4 +1,4 @@ -<% target = "#{dom_id(@text_field.character_sheet_section)}_add_section" %> +<% target = "#{dom_id(@section)}_add_section" %> <%= turbo_stream.replace(target) do %>
<%= render partial: "form", locals: { button_text: t(".create_text_field") } %> diff --git a/db/migrate/20240617112523_create_character_sheet_features.rb b/db/migrate/20240617112523_create_character_sheet_features.rb new file mode 100644 index 0000000..67115e9 --- /dev/null +++ b/db/migrate/20240617112523_create_character_sheet_features.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class CreateCharacterSheetFeatures < ActiveRecord::Migration[7.1] + def change + create_table :character_sheet_features, primary_key: [ :character_sheet_section_id, :featurable_id ] do |t| + t.belongs_to :character_sheet_section, null: false, foreign_key: true + t.belongs_to :featurable, null: false, polymorphic: true + t.integer :order, null: false + t.string :slug, null: false + + t.timestamps + end + + remove_column :counters, :character_sheet_section_id, :integer + remove_column :stats, :character_sheet_section_id, :integer + remove_column :text_fields, :character_sheet_section_id, :integer + + remove_column :counters, :order_index, :integer + remove_column :stats, :order_index, :integer + remove_column :text_fields, :order_index, :integer + + remove_column :counters, :slug, :string + remove_column :stats, :slug, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 65c2262..c44ae48 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do +ActiveRecord::Schema[7.1].define(version: 2024_06_17_112523) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -52,6 +52,18 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end + create_table "character_sheet_features", primary_key: ["character_sheet_section_id", "featurable_id"], force: :cascade do |t| + t.bigint "character_sheet_section_id", null: false + t.string "featurable_type", null: false + t.bigint "featurable_id", null: false + t.integer "order", null: false + t.string "slug", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["character_sheet_section_id"], name: "index_character_sheet_features_on_character_sheet_section_id" + t.index ["featurable_type", "featurable_id"], name: "index_character_sheet_features_on_featurable" + end + create_table "character_sheet_sections", force: :cascade do |t| t.string "name", null: false t.bigint "character_id", null: false @@ -78,16 +90,10 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do create_table "counters", force: :cascade do |t| t.string "name", null: false - t.string "slug", null: false t.integer "value", default: 0, null: false - t.bigint "character_sheet_section_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["character_sheet_section_id"], name: "index_counters_on_character_sheet_section_id" - t.index ["name", "character_sheet_section_id"], name: "index_counters_on_name_and_character_sheet_section_id", unique: true - t.index ["slug"], name: "index_counters_on_slug", unique: true t.check_constraint "length(name::text) <= 100", name: "chk_counter_name_max_length" - t.check_constraint "length(slug::text) <= 100", name: "chk_counter_slug_max_length" end create_table "dice_rolls", force: :cascade do |t| @@ -236,17 +242,11 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do create_table "stats", force: :cascade do |t| t.string "name", null: false - t.string "slug", null: false - t.bigint "character_sheet_section_id", null: false t.integer "value", default: 0, null: false t.string "roll_command" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["character_sheet_section_id"], name: "index_stats_on_character_sheet_section_id" - t.index ["name", "character_sheet_section_id"], name: "index_stats_on_name_and_character_sheet_section_id", unique: true - t.index ["slug"], name: "index_stats_on_slug", unique: true t.check_constraint "length(name::text) <= 100", name: "chk_stat_name_max_length" - t.check_constraint "length(slug::text) <= 100", name: "chk_stat_slug_max_length" end create_table "table_invites", force: :cascade do |t| @@ -277,11 +277,9 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do end create_table "text_fields", force: :cascade do |t| - t.bigint "character_sheet_section_id", null: false t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["character_sheet_section_id"], name: "index_text_fields_on_character_sheet_section_id" t.check_constraint "length(name::text) <= 100", name: "chk_text_field_name_max_length" end @@ -305,12 +303,12 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do 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 "character_sheet_features", "character_sheet_sections" add_foreign_key "character_sheet_sections", "character_sheet_sections", column: "parent_section_id" add_foreign_key "character_sheet_sections", "characters" add_foreign_key "characters", "game_systems" add_foreign_key "characters", "tables" add_foreign_key "characters", "users" - add_foreign_key "counters", "character_sheet_sections" add_foreign_key "dice_rolls", "tables" add_foreign_key "players", "tables" add_foreign_key "players", "users" @@ -320,9 +318,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_06_14_084224) do add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade - add_foreign_key "stats", "character_sheet_sections" add_foreign_key "table_invites", "tables" add_foreign_key "tables", "game_systems" add_foreign_key "tables", "users", column: "owner_id" - add_foreign_key "text_fields", "character_sheet_sections" end diff --git a/test/fixtures/character_sheet_features.yml b/test/fixtures/character_sheet_features.yml new file mode 100644 index 0000000..8f48837 --- /dev/null +++ b/test/fixtures/character_sheet_features.yml @@ -0,0 +1,20 @@ +DEFAULTS: &DEFAULTS + slug: $LABEL + +stats-strength: + <<: *DEFAULTS + character_sheet_section: stats + featurable: strength (Stat) + order: 1 + +counter: + <<: *DEFAULTS + character_sheet_section: counters + featurable: hp (Counter) + order: 2 + +text_field: + <<: *DEFAULTS + character_sheet_section: info + featurable: background (TextField) + order: 3 diff --git a/test/fixtures/counters.yml b/test/fixtures/counters.yml index 1c83932..513ddf7 100644 --- a/test/fixtures/counters.yml +++ b/test/fixtures/counters.yml @@ -1,5 +1,3 @@ hp: name: HP value: 10 - slug: hp - character_sheet_section: counters diff --git a/test/fixtures/stats.yml b/test/fixtures/stats.yml index 6d9cb3c..6761014 100644 --- a/test/fixtures/stats.yml +++ b/test/fixtures/stats.yml @@ -1,6 +1,4 @@ strength: name: Strength - slug: strength - character_sheet_section: stats value: 10 roll_command: d20+self diff --git a/test/fixtures/text_fields.yml b/test/fixtures/text_fields.yml index f3067cd..0eeb7d4 100644 --- a/test/fixtures/text_fields.yml +++ b/test/fixtures/text_fields.yml @@ -1,3 +1,2 @@ background: - character_sheet_section: info name: Background diff --git a/test/models/character_sheet_feature_test.rb b/test/models/character_sheet_feature_test.rb new file mode 100644 index 0000000..48bdfc5 --- /dev/null +++ b/test/models/character_sheet_feature_test.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "test_helper" + +class CharacterSheetFeatureTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/stat_test.rb b/test/models/stat_test.rb index 283152a..775807a 100644 --- a/test/models/stat_test.rb +++ b/test/models/stat_test.rb @@ -13,14 +13,16 @@ class StatTest < ActiveSupport::TestCase test "saving generates appropriate slug" do stat = Stat.create(name: "Foo", character_sheet_section: character_sheet_sections(:subsection)) - assert_equal "stats-foo", stat.slug + assert_equal "stats-foo", stat.character_sheet_feature.slug end test "generates unique slug always" do existing_stat = Stat.first - stat = Stat.create(name: existing_stat.name, character_sheet_section: existing_stat.character_sheet_section) - assert_not_equal existing_stat.slug, stat.slug - assert_equal "#{existing_stat.slug}-2", stat.slug + stat = Stat.create(name: existing_stat.name) + stat.build_character_sheet_feature(character_sheet_section: character_sheet_sections(:subsection)) + stat.valid? + assert_not_equal existing_stat.character_sheet_feature.slug, stat.character_sheet_feature.slug + assert_equal "#{existing_stat.character_sheet_feature.slug}-2", stat.character_sheet_feature.slug end test "rolls with roll_command" do