From 13fea3fb522e26575d452a959c463d5136ea82f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:15:57 +0000 Subject: [PATCH 1/2] Add utf8mb4 migration and tests for emoji support Co-authored-by: moio <250541+moio@users.noreply.github.com> Signed-off-by: Silvio Moioli --- config/database.yml.example | 3 ++- .../20251202131317_convert_to_utf8mb4.rb | 27 +++++++++++++++++++ spec/models/comment_spec.rb | 9 +++++++ spec/models/project_spec.rb | 16 +++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20251202131317_convert_to_utf8mb4.rb diff --git a/config/database.yml.example b/config/database.yml.example index 61d76f881..c6c7bd761 100644 --- a/config/database.yml.example +++ b/config/database.yml.example @@ -6,7 +6,8 @@ default: &default adapter: mysql2 - encoding: utf8 + encoding: utf8mb4 + collation: utf8mb4_unicode_ci username: root password: root host: <%= host %> diff --git a/db/migrate/20251202131317_convert_to_utf8mb4.rb b/db/migrate/20251202131317_convert_to_utf8mb4.rb new file mode 100644 index 000000000..ac9b1ac6c --- /dev/null +++ b/db/migrate/20251202131317_convert_to_utf8mb4.rb @@ -0,0 +1,27 @@ +class ConvertToUtf8mb4 < ActiveRecord::Migration[7.2] + # Tables that need utf8mb4 for emoji support in their text fields + TABLES_TO_CONVERT = %w[ + announcements + comments + faqs + projects + updates + ].freeze + + def up + # Convert the database default charset + execute "ALTER DATABASE `#{connection.current_database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + + TABLES_TO_CONVERT.each do |table| + execute "ALTER TABLE #{table} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + end + end + + def down + TABLES_TO_CONVERT.each do |table| + execute "ALTER TABLE #{table} CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;" + end + + execute "ALTER DATABASE `#{connection.current_database}` CHARACTER SET utf8 COLLATE utf8_general_ci;" + end +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index 96033eb55..e9f227f9f 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -6,4 +6,13 @@ comment.destroy! expect(Comment.count).to eq 0 end + + describe 'emoji support' do + it 'saves and retrieves comments with emoji characters' do + emoji_text = 'This is a great idea! 😱🎉👍' + comment = create(:comment, text: emoji_text) + comment.reload + expect(comment.text).to eq(emoji_text) + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index fb285909b..534382e84 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -175,4 +175,20 @@ expect(Notification.where(recipient: user).count).to eq(1) end end + + describe 'emoji support' do + it 'saves and retrieves project descriptions with emoji characters' do + emoji_description = 'Project with emojis 😱🎉👍 and more text' + project = create(:project, description: emoji_description) + project.reload + expect(project.description).to eq(emoji_description) + end + + it 'saves and retrieves project titles with emoji characters' do + emoji_title = 'My Awesome Project 🚀' + project = create(:project, title: emoji_title) + project.reload + expect(project.title).to eq(emoji_title) + end + end end From 24d64444855c91f6ea1999c77600fbc6119ea095 Mon Sep 17 00:00:00 2001 From: SMOIOLI Date: Fri, 12 Dec 2025 15:29:47 +0100 Subject: [PATCH 2/2] refresh schema --- db/schema.rb | 80 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 8eba14745..03f6e871e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,19 +10,18 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_10_21_103843) do - - create_table "active_storage_attachments", charset: "utf8", force: :cascade do |t| +ActiveRecord::Schema[7.2].define(version: 2025_12_02_131317) do + create_table "active_storage_attachments", charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", force: :cascade do |t| t.string "name", null: false t.string "record_type", null: false t.bigint "record_id", null: false t.bigint "blob_id", null: false - t.datetime "created_at", precision: 6, null: false + t.datetime "created_at", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end - create_table "active_storage_blobs", charset: "utf8", force: :cascade do |t| + create_table "active_storage_blobs", charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", force: :cascade do |t| t.string "key", null: false t.string "filename", null: false t.string "content_type" @@ -30,26 +29,26 @@ t.string "service_name", null: false t.bigint "byte_size", null: false t.string "checksum" - t.datetime "created_at", precision: 6, null: false + t.datetime "created_at", null: false t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end - create_table "active_storage_variant_records", charset: "utf8", force: :cascade do |t| + create_table "active_storage_variant_records", charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", force: :cascade do |t| t.bigint "blob_id", null: false t.string "variation_digest", null: false t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true end - create_table "announcements", id: :integer, charset: "utf8", collation: "utf8_bin", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "announcements", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.string "title" - t.text "text", size: :medium + t.text "text", size: :long t.integer "originator_id" t.datetime "created_at" t.datetime "updated_at" end - create_table "comments", id: :integer, charset: "utf8", collation: "utf8_bin", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| - t.text "text" + create_table "comments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + t.text "text", size: :medium t.integer "commentable_id" t.string "commentable_type" t.integer "commenter_id" @@ -58,14 +57,14 @@ t.boolean "ham" end - create_table "enrollments", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "enrollments", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "announcement_id" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end - create_table "episodes", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "episodes", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.string "name" t.date "start_date" t.date "end_date" @@ -75,7 +74,7 @@ t.text "description" end - create_table "episodes_projects", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "episodes_projects", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "episode_id" t.integer "project_id" t.datetime "created_at" @@ -84,14 +83,14 @@ t.index ["project_id"], name: "index_episodes_projects_on_project_id" end - create_table "faqs", charset: "utf8", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| - t.text "question" - t.text "answer" + create_table "faqs", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + t.text "question", size: :medium + t.text "answer", size: :medium t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "impressions", charset: "utf8", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "impressions", charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.string "impressionable_type" t.integer "impressionable_id" t.integer "user_id" @@ -117,7 +116,7 @@ t.index ["user_id"], name: "index_impressions_on_user_id" end - create_table "keywords", id: :integer, charset: "utf8", collation: "utf8_bin", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "keywords", id: :integer, charset: "utf8mb3", collation: "utf8mb3_bin", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.text "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false @@ -128,35 +127,35 @@ t.string "description" end - create_table "keywords_projects", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "keywords_projects", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "project_id" t.integer "keyword_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "keywords_users", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "keywords_users", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "user_id" t.integer "keyword_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "likes", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "likes", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "project_id" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "memberships", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "memberships", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "project_id" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "notifications", id: :integer, charset: "utf8", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "notifications", id: :integer, charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "recipient_id" t.integer "actor_id" t.datetime "read_at" @@ -167,16 +166,23 @@ t.datetime "updated_at", null: false end - create_table "project_follows", id: :integer, charset: "utf8", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "project_follows", id: :integer, charset: "utf8mb3", collation: "utf8mb3_uca1400_ai_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "project_id" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "projects", id: :integer, charset: "utf8", collation: "utf8_bin", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| - t.text "title" - t.text "description" + create_table "project_interests", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.integer "project_id" + t.integer "keyword_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "projects", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + t.text "title", size: :medium + t.text "description", size: :medium t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "originator_id" @@ -191,26 +197,33 @@ t.integer "projecthits", default: 0 end - create_table "roles", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "roles", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" end - create_table "roles_users", id: false, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "roles_users", id: false, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.integer "role_id" t.integer "user_id" end - create_table "updates", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| - t.text "text" + create_table "updates", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + t.text "text", size: :medium t.integer "author_id" t.integer "project_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "users", id: :integer, charset: "latin1", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| + create_table "user_interests", id: :integer, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.integer "user_id" + t.integer "keyword_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "users", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", options: "ENGINE=InnoDB ROW_FORMAT=DYNAMIC", force: :cascade do |t| t.string "uid" t.string "name" t.string "email" @@ -231,6 +244,5 @@ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end - 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" end