dry-rb / dry-inflector

Inflector for Ruby
https://dry-rb.org/gems/dry-inflector
MIT License
95 stars 15 forks source link

Unexpected singularize inflection `video_analyses` -> `video_analyasis` #46

Closed parndt closed 1 year ago

parndt commented 1 year ago

Describe the bug

When using Dry::Inflector.new.singularize I get strange results.

To Reproduce

irb(main):001:0> require "dry-inflector"
=> true
irb(main):002:0> Dry::Inflector.new.singularize "analyses"
=> "analysis"
irb(main):003:0> Dry::Inflector.new.singularize "_analyses"
=> "_analyasis"
irb(main):004:0> Dry::Inflector.new.singularize "video_analyses"
=> "video_analyasis"

Expected behavior

I would expect it to be _analysis or video_analysis

My environment

parndt commented 1 year ago

Here is a failing spec:

RSpec.describe Dry::Inflector do
  describe "#singularize" do
    Fixtures::Singularize.cases.each do |plural, singular|
      it "singularizes #{plural} => #{singular}" do
        expect(subject.singularize(i(plural))).to eq(singular)
      end

      it "singularizes _#{plural} => _#{singular}" do
        expect(subject.singularize(i("_#{plural}"))).to eq("_#{singular}")
      end
    end

This yields:

Failures:

  1) Dry::Inflector#singularize singularizes _oxen => _ox
     Failure/Error: expect(subject.singularize(i("_#{plural}"))).to eq("_#{singular}")

       expected: "_ox"
            got: "_oxen"

       (compared using ==)
     # ./spec/unit/dry/inflector/singularize_spec.rb:11:in `block (4 levels) in <top (required)>'

  2) Dry::Inflector#singularize singularizes _analysis => _analysis
     Failure/Error: expect(subject.singularize(i("_#{plural}"))).to eq("_#{singular}")

       expected: "_analysis"
            got: "_analyasis"

       (compared using ==)
     # ./spec/unit/dry/inflector/singularize_spec.rb:11:in `block (4 levels) in <top (required)>'

  3) Dry::Inflector#singularize singularizes _species => _species
     Failure/Error: expect(subject.singularize(i("_#{plural}"))).to eq("_#{singular}")

       expected: "_species"
            got: "_specy"

       (compared using ==)
     # ./spec/unit/dry/inflector/singularize_spec.rb:11:in `block (4 levels) in <top (required)>'

  4) Dry::Inflector#singularize singularizes _analyses => _analysis
     Failure/Error: expect(subject.singularize(i("_#{plural}"))).to eq("_#{singular}")

       expected: "_analysis"
            got: "_analyasis"

       (compared using ==)
     # ./spec/unit/dry/inflector/singularize_spec.rb:11:in `block (4 levels) in <top (required)>'
parndt commented 1 year ago

This seems to be happening because the rule gets compiled as this:

=> /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)\z/i

which leads to: Dry::Inflector::Rules#apply_to(word):

[9] pry(#<Dry::Inflector::Rules>)> result.scan(rule)
=> [["analy", "a", nil, nil, nil, nil, nil, nil, "sis"]]
[11] pry(#<Dry::Inflector::Rules>)> replacement
=> "\\1\\2sis"

But we can see different results:

[15] pry(#<Dry::Inflector::Rules>)> result.gsub(rule, "\\1\\2sis")
=> "_analyasis"
[16] pry(#<Dry::Inflector::Rules>)> result.gsub(rule, "\\1sis")
=> "_analysis"

This indicates that if the rule were instead:

-=> /((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)\z/i
+=> /(analy|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)\z/i

then it would work

parndt commented 1 year ago

As an aside, I've also discovered similar failures if I do the same thing to the pluralize_spec:

RSpec.describe Dry::Inflector do
  describe "#pluralize" do
    Fixtures::Pluralize.cases.each do |singular, plural|
      it "pluralizes #{singular} => #{plural}" do
        expect(subject.pluralize(i(singular))).to eq(plural)
      end

      it "pluralizes _#{singular} to _#{plural}" do
        expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")
      end
    end
Failures:

  1) Dry::Inflector#pluralize pluralizes _moose to _moose
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_moose"
            got: "_mooses"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  2) Dry::Inflector#pluralize pluralizes _equipment to _equipment
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_equipment"
            got: "_equipments"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  3) Dry::Inflector#pluralize pluralizes _rain to _rain
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_rain"
            got: "_rains"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  4) Dry::Inflector#pluralize pluralizes _hovercraft to _hovercraft
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_hovercraft"
            got: "_hovercrafts"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  5) Dry::Inflector#pluralize pluralizes _sheep to _sheep
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_sheep"
            got: "_sheeps"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  6) Dry::Inflector#pluralize pluralizes _information to _information
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_information"
            got: "_informations"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  7) Dry::Inflector#pluralize pluralizes _ox to _oxen
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_oxen"
            got: "_oxes"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  8) Dry::Inflector#pluralize pluralizes _money to _money
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_money"
            got: "_moneys"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  9) Dry::Inflector#pluralize pluralizes _milk to _milk
     Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

       expected: "_milk"
            got: "_milks"

       (compared using ==)
     # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  10) Dry::Inflector#pluralize pluralizes _grass to _grass
      Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

        expected: "_grass"
             got: "_grasses"

        (compared using ==)
      # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  11) Dry::Inflector#pluralize pluralizes _deer to _deer
      Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

        expected: "_deer"
             got: "_deers"

        (compared using ==)
      # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  12) Dry::Inflector#pluralize pluralizes _rice to _rice
      Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

        expected: "_rice"
             got: "_rices"

        (compared using ==)
      # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'

  13) Dry::Inflector#pluralize pluralizes _fish to _fish
      Failure/Error: expect(subject.pluralize(i("_#{singular}"))).to eq("_#{plural}")

        expected: "_fish"
             got: "_fishes"

        (compared using ==)
      # ./spec/unit/dry/inflector/pluralize_spec.rb:11:in `block (4 levels) in <top (required)>'
jodosha commented 1 year ago

Fixed by #47