From aa5cb535faef325ef06cc2e0e5851213c93630fb Mon Sep 17 00:00:00 2001 From: Anna Buianova Date: Mon, 11 Sep 2023 18:44:19 +0300 Subject: [PATCH] Add languages to User model (#20059) --- app/models/user.rb | 2 ++ app/models/user_language.rb | 5 +++++ app/services/languages/detection.rb | 4 ++++ .../20230906112602_create_user_languages.rb | 10 +++++++++ db/schema.rb | 11 +++++++++- spec/factories/user_languages.rb | 6 +++++ spec/models/user_language_spec.rb | 22 +++++++++++++++++++ spec/models/user_spec.rb | 1 + spec/services/languages/detection_spec.rb | 6 +++++ 9 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 app/models/user_language.rb create mode 100644 db/migrate/20230906112602_create_user_languages.rb create mode 100644 spec/factories/user_languages.rb create mode 100644 spec/models/user_language_spec.rb diff --git a/app/models/user.rb b/app/models/user.rb index 8e368cbda..1c39eb7ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -115,6 +115,8 @@ class User < ApplicationRecord has_many :subscribers, through: :source_authored_user_subscriptions, dependent: :destroy has_many :tweets, dependent: :nullify has_many :devices, dependent: :delete_all + # languages that user undestands + has_many :languages, class_name: "UserLanguage", inverse_of: :user, dependent: :delete_all mount_uploader :profile_image, ProfileImageUploader diff --git a/app/models/user_language.rb b/app/models/user_language.rb new file mode 100644 index 000000000..42e7e9015 --- /dev/null +++ b/app/models/user_language.rb @@ -0,0 +1,5 @@ +class UserLanguage < ApplicationRecord + belongs_to :user, inverse_of: :languages + + validates :language, inclusion: { in: Languages::Detection.codes }, presence: true +end diff --git a/app/services/languages/detection.rb b/app/services/languages/detection.rb index b5c6137fd..9c660b45c 100644 --- a/app/services/languages/detection.rb +++ b/app/services/languages/detection.rb @@ -4,6 +4,10 @@ module Languages PROBABILITY_THRESHOLD = 0.5 + def self.codes + CLD3::TaskContextParams::LANGUAGE_NAMES.map(&:to_s).freeze + end + def self.call(...) new(...).call end diff --git a/db/migrate/20230906112602_create_user_languages.rb b/db/migrate/20230906112602_create_user_languages.rb new file mode 100644 index 000000000..f05ae52b9 --- /dev/null +++ b/db/migrate/20230906112602_create_user_languages.rb @@ -0,0 +1,10 @@ +class CreateUserLanguages < ActiveRecord::Migration[7.0] + def change + create_table :user_languages do |t| + t.references :user, foreign_key: true, null: false + t.string :language, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0fb8712ca..1a1fdfdbf 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.0].define(version: 2023_08_31_160030) do +ActiveRecord::Schema[7.0].define(version: 2023_09_06_112602) do # These are extensions that must be enabled in order to support this database enable_extension "citext" enable_extension "ltree" @@ -1187,6 +1187,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_31_160030) do t.index ["blocked_id", "blocker_id"], name: "index_user_blocks_on_blocked_id_and_blocker_id", unique: true end + create_table "user_languages", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "language", null: false + t.datetime "updated_at", null: false + t.bigint "user_id", null: false + t.index ["user_id"], name: "index_user_languages_on_user_id" + end + create_table "user_subscriptions", force: :cascade do |t| t.bigint "author_id", null: false t.datetime "created_at", null: false @@ -1454,6 +1462,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_31_160030) do add_foreign_key "tweets", "users", on_delete: :nullify add_foreign_key "user_blocks", "users", column: "blocked_id" add_foreign_key "user_blocks", "users", column: "blocker_id" + add_foreign_key "user_languages", "users" add_foreign_key "user_subscriptions", "users", column: "author_id" add_foreign_key "user_subscriptions", "users", column: "subscriber_id" add_foreign_key "users_notification_settings", "users" diff --git a/spec/factories/user_languages.rb b/spec/factories/user_languages.rb new file mode 100644 index 000000000..ab9c736ff --- /dev/null +++ b/spec/factories/user_languages.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :user_language do + user + language { :en } + end +end diff --git a/spec/models/user_language_spec.rb b/spec/models/user_language_spec.rb new file mode 100644 index 000000000..2d1c596dd --- /dev/null +++ b/spec/models/user_language_spec.rb @@ -0,0 +1,22 @@ +require "rails_helper" + +RSpec.describe UserLanguage do + describe "validations" do + subject { build(:user_language) } + + describe "builtin validations" do + it { is_expected.to belong_to(:user) } + it { is_expected.to validate_presence_of(:language) } + end + + it "actually validates language" do + expect(build(:user_language, language: :es)).to be_valid + end + + it "actually validates language (invalid)" do + lang = build(:user_language, language: :abracadabra) + expect(lang).not_to be_valid + expect(lang.errors[:language]).to be_present + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 6172ff99b..b04bfe6ea 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -112,6 +112,7 @@ RSpec.describe User do it { is_expected.to have_many(:subscribed_to_user_subscriptions).dependent(:destroy) } it { is_expected.to have_many(:subscribers).dependent(:destroy) } it { is_expected.to have_many(:tweets).dependent(:nullify) } + it { is_expected.to have_many(:languages).dependent(:delete_all) } # rubocop:disable RSpec/NamedSubject it do diff --git a/spec/services/languages/detection_spec.rb b/spec/services/languages/detection_spec.rb index e29519e93..201608708 100644 --- a/spec/services/languages/detection_spec.rb +++ b/spec/services/languages/detection_spec.rb @@ -3,6 +3,12 @@ require "rails_helper" RSpec.describe Languages::Detection, type: :service do subject(:language_detection) { described_class.call(text) } + describe "codes" do + it "returns an array including a couple of popular languages" do + expect(described_class.codes).to include("en", "es", "fr") + end + end + context "when the text is clearly identifiable as English" do let(:text) { "This is clearly English text." }