diff --git a/app/models/rosetta/locale.rb b/app/models/rosetta/locale.rb index 4230273..66ed592 100644 --- a/app/models/rosetta/locale.rb +++ b/app/models/rosetta/locale.rb @@ -10,6 +10,7 @@ class Locale < ApplicationRecord has_many :text_entries, dependent: :destroy after_create_commit :notify_translated_models + has_many :pluralization_rules, dependent: :destroy class << self def available_locales diff --git a/app/models/rosetta/pluralization_rule.rb b/app/models/rosetta/pluralization_rule.rb new file mode 100644 index 0000000..c6ae696 --- /dev/null +++ b/app/models/rosetta/pluralization_rule.rb @@ -0,0 +1,21 @@ +module Rosetta + class PluralizationRule < ApplicationRecord + OPERATORS = { + less_than_or_equal_to: Pluralization::Operators::LessThanOrEqualTo, + less_than: Pluralization::Operators::LessThan, + equal_to: Pluralization::Operators::EqualTo, + greater_than_or_equal_to: Pluralization::Operators::GreaterThanOrEqualTo, + greater_than: Pluralization::Operators::GreaterThan + }.with_indifferent_access + + belongs_to :locale + + validates :name, :operator, :threshold, presence: true + validates :threshold, numericality: { greater_than_or_equal_to: 0 } + validates :operator, inclusion: { in: OPERATORS.keys } + + def activated?(test_value) + OPERATORS[operator].new(value: test_value, threshold: threshold).check + end + end +end diff --git a/db/migrate/20240920134441_create_rosetta_pluralization_rules.rb b/db/migrate/20240920134441_create_rosetta_pluralization_rules.rb new file mode 100644 index 0000000..44897ad --- /dev/null +++ b/db/migrate/20240920134441_create_rosetta_pluralization_rules.rb @@ -0,0 +1,14 @@ +class CreateRosettaPluralizationRules < ActiveRecord::Migration[6.1] + def change + create_table :rosetta_pluralization_rules do |t| + t.string :name + t.string :operator + t.integer :threshold + t.references :locale, null: false + + t.timestamps + + t.foreign_key :rosetta_locales, column: :locale_id + end + end +end diff --git a/db/migrate/20240923100651_add_default_to_rosetta_locales.rb b/db/migrate/20240923100651_add_default_to_rosetta_locales.rb index 4e75a6f..64b487d 100644 --- a/db/migrate/20240923100651_add_default_to_rosetta_locales.rb +++ b/db/migrate/20240923100651_add_default_to_rosetta_locales.rb @@ -1,4 +1,4 @@ -class AddDefaultToRosettaLocales < ActiveRecord::Migration[7.2] +class AddDefaultToRosettaLocales < ActiveRecord::Migration[6.1] def change add_column :rosetta_locales, :default, :boolean, default: false end diff --git a/lib/rosetta-rails.rb b/lib/rosetta-rails.rb index 2f4719c..29533ce 100644 --- a/lib/rosetta-rails.rb +++ b/lib/rosetta-rails.rb @@ -9,6 +9,13 @@ require "rosetta/translated/create" require "rosetta/translated/delete" +require "rosetta/pluralization/base_operator" +require "rosetta/pluralization/operators/less_than_or_equal_to" +require "rosetta/pluralization/operators/less_than" +require "rosetta/pluralization/operators/equal_to" +require "rosetta/pluralization/operators/greater_than_or_equal_to" +require "rosetta/pluralization/operators/greater_than" + module Rosetta module Base def locale diff --git a/lib/rosetta/pluralization/base_operator.rb b/lib/rosetta/pluralization/base_operator.rb new file mode 100644 index 0000000..e3be20d --- /dev/null +++ b/lib/rosetta/pluralization/base_operator.rb @@ -0,0 +1,14 @@ +module Rosetta + module Pluralization + class BaseOperator + def initialize(value:, threshold:) + @value = value + @threshold = threshold + end + + def check + raise NotImplementedError + end + end + end +end diff --git a/lib/rosetta/pluralization/operators/equal_to.rb b/lib/rosetta/pluralization/operators/equal_to.rb new file mode 100644 index 0000000..907b7a6 --- /dev/null +++ b/lib/rosetta/pluralization/operators/equal_to.rb @@ -0,0 +1,11 @@ +module Rosetta + module Pluralization + module Operators + class EqualTo < BaseOperator + def check + @value == @threshold + end + end + end + end +end diff --git a/lib/rosetta/pluralization/operators/greater_than.rb b/lib/rosetta/pluralization/operators/greater_than.rb new file mode 100644 index 0000000..5623ffd --- /dev/null +++ b/lib/rosetta/pluralization/operators/greater_than.rb @@ -0,0 +1,11 @@ +module Rosetta + module Pluralization + module Operators + class GreaterThan < BaseOperator + def check + @value > @threshold + end + end + end + end +end diff --git a/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb b/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb new file mode 100644 index 0000000..ff0a61c --- /dev/null +++ b/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb @@ -0,0 +1,11 @@ +module Rosetta + module Pluralization + module Operators + class GreaterThanOrEqualTo < BaseOperator + def check + @value >= @threshold + end + end + end + end +end diff --git a/lib/rosetta/pluralization/operators/less_than.rb b/lib/rosetta/pluralization/operators/less_than.rb new file mode 100644 index 0000000..9d0b1fb --- /dev/null +++ b/lib/rosetta/pluralization/operators/less_than.rb @@ -0,0 +1,11 @@ +module Rosetta + module Pluralization + module Operators + class LessThan < BaseOperator + def check + @value < @threshold + end + end + end + end +end diff --git a/lib/rosetta/pluralization/operators/less_than_or_equal_to.rb b/lib/rosetta/pluralization/operators/less_than_or_equal_to.rb new file mode 100644 index 0000000..33f705d --- /dev/null +++ b/lib/rosetta/pluralization/operators/less_than_or_equal_to.rb @@ -0,0 +1,11 @@ +module Rosetta + module Pluralization + module Operators + class LessThanOrEqualTo < BaseOperator + def check + @value <= @threshold + end + end + end + end +end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index a8f3fa8..fc48c08 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_10_02_152043) do +ActiveRecord::Schema[7.2].define(version: 2024_10_02_152043) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -24,6 +24,16 @@ t.index ["code"], name: "index_rosetta_locales_on_code", unique: true end + create_table "rosetta_pluralization_rules", force: :cascade do |t| + t.string "name" + t.string "operator" + t.integer "threshold" + t.bigint "locale_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["locale_id"], name: "index_rosetta_pluralization_rules_on_locale_id" + end + create_table "rosetta_text_entries", force: :cascade do |t| t.text "content", null: false t.bigint "locale_id", null: false @@ -46,6 +56,7 @@ t.index ["to_id"], name: "index_rosetta_translations_on_to_id" end + add_foreign_key "rosetta_pluralization_rules", "rosetta_locales", column: "locale_id" add_foreign_key "rosetta_text_entries", "rosetta_locales", column: "locale_id" add_foreign_key "rosetta_translations", "rosetta_locales", column: "target_locale_id" add_foreign_key "rosetta_translations", "rosetta_text_entries", column: "from_id" diff --git a/test/fixtures/rosetta/pluralization_rules.yml b/test/fixtures/rosetta/pluralization_rules.yml new file mode 100644 index 0000000..44b0535 --- /dev/null +++ b/test/fixtures/rosetta/pluralization_rules.yml @@ -0,0 +1,7 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +french_less_than_or_equal_to_one: + name: Less than or equal to one + operator: less_than_or_equal_to + threshold: 1 + locale: french diff --git a/test/helpers/pluralization_operator_helpers.rb b/test/helpers/pluralization_operator_helpers.rb new file mode 100644 index 0000000..cfed219 --- /dev/null +++ b/test/helpers/pluralization_operator_helpers.rb @@ -0,0 +1,5 @@ +module PluralizationOperatorHelpers + def operator_class + self.class.name.gsub(/Test\z/, "").constantize + end +end diff --git a/test/lib/rosetta/pluralization/operators/equal_to_test.rb b/test/lib/rosetta/pluralization/operators/equal_to_test.rb new file mode 100644 index 0000000..16763ab --- /dev/null +++ b/test/lib/rosetta/pluralization/operators/equal_to_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Rosetta::Pluralization::Operators::EqualToTest < ActiveSupport::TestCase + include PluralizationOperatorHelpers + + test "checks correct counts" do + assert operator_class.new(value: 1, threshold: 1).check + assert_not operator_class.new(value: 2, threshold: 1).check + assert_not operator_class.new(value: 0, threshold: 1).check + end +end diff --git a/test/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb b/test/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb new file mode 100644 index 0000000..5a5cc5e --- /dev/null +++ b/test/lib/rosetta/pluralization/operators/greater_than_or_equal_to.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Rosetta::Pluralization::Operators::GreaterThanOrEqualToTest < ActiveSupport::TestCase + include PluralizationOperatorHelpers + + test "checks correct counts" do + assert operator_class.new(value: 1, threshold: 1).check + assert operator_class.new(value: 2, threshold: 1).check + assert_not operator_class.new(value: 0, threshold: 1).check + end +end diff --git a/test/lib/rosetta/pluralization/operators/greater_than_test.rb b/test/lib/rosetta/pluralization/operators/greater_than_test.rb new file mode 100644 index 0000000..fce1b18 --- /dev/null +++ b/test/lib/rosetta/pluralization/operators/greater_than_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Rosetta::Pluralization::Operators::GreaterThanTest < ActiveSupport::TestCase + include PluralizationOperatorHelpers + + test "checks correct counts" do + assert operator_class.new(value: 2, threshold: 1).check + assert_not operator_class.new(value: 1, threshold: 1).check + assert_not operator_class.new(value: 0, threshold: 1).check + end +end diff --git a/test/lib/rosetta/pluralization/operators/less_than_or_equal_to_test.rb b/test/lib/rosetta/pluralization/operators/less_than_or_equal_to_test.rb new file mode 100644 index 0000000..bbb943d --- /dev/null +++ b/test/lib/rosetta/pluralization/operators/less_than_or_equal_to_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Rosetta::Pluralization::Operators::LessThanOrEqualToTest < ActiveSupport::TestCase + include PluralizationOperatorHelpers + + test "checks correct counts" do + assert operator_class.new(value: 2, threshold: 2).check + assert operator_class.new(value: 1, threshold: 2).check + assert_not operator_class.new(value: 5, threshold: 2).check + end +end diff --git a/test/lib/rosetta/pluralization/operators/less_than_test.rb b/test/lib/rosetta/pluralization/operators/less_than_test.rb new file mode 100644 index 0000000..020d858 --- /dev/null +++ b/test/lib/rosetta/pluralization/operators/less_than_test.rb @@ -0,0 +1,11 @@ +require "test_helper" + +class Rosetta::Pluralization::Operators::LessThanTest < ActiveSupport::TestCase + include PluralizationOperatorHelpers + + test "checks correct counts" do + assert operator_class.new(value: 1, threshold: 2).check + assert_not operator_class.new(value: 2, threshold: 2).check + assert_not operator_class.new(value: 5, threshold: 2).check + end +end diff --git a/test/models/rosetta/pluralization_rule_test.rb b/test/models/rosetta/pluralization_rule_test.rb new file mode 100644 index 0000000..a4860c9 --- /dev/null +++ b/test/models/rosetta/pluralization_rule_test.rb @@ -0,0 +1,39 @@ +require "test_helper" + +module Rosetta + class PluralizationRuleTest < ActiveSupport::TestCase + setup do + @rule = rosetta_pluralization_rules(:french_less_than_or_equal_to_one) + end + + test "valid" do + assert @rule.valid? + end + + %i[name operator threshold].each do |attribute| + test "invalid without #{attribute}" do + @rule.public_send("#{attribute}=", nil) + assert_not @rule.valid? + assert_includes @rule.errors[attribute], "can't be blank" + end + end + + test "operator must be included in the list" do + @rule.operator = "unknown_operator" + assert_not @rule.valid? + assert_includes @rule.errors[:operator], "is not included in the list" + end + + test "value must be positive" do + @rule.threshold = -1 + assert_not @rule.valid? + assert_includes @rule.errors[:threshold], "must be greater than or equal to 0" + end + + test "#activated?" do + assert @rule.activated?(0) + assert @rule.activated?(1) + assert_not @rule.activated?(2) + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index ea4538d..bf4b086 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,6 +6,8 @@ ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__) require "rails/test_help" +require_relative "./helpers/pluralization_operator_helpers" + # Load fixtures from the engine if ActiveSupport::TestCase.respond_to?(:fixture_paths=) ActiveSupport::TestCase.fixture_paths = [ File.expand_path("fixtures", __dir__) ]