1

I'm having a strange problem with Rails 4.2.4. I am creating a new table, which references some others, like this:

t.references :local, index: true, foreign_key: true, null: false
t.references :serie, index: true, foreign_key: true, null: false

when I execute the migration, there is an error when creating the foreign key constraint:

PG::UndefinedColumn: ERROR: no existe la columna «series_id» referida en la llave foránea

which is Spanish for

PG::UndefinedColumn: ERROR: column «series_id» referenced in foreign key constraint does not exist

meaning there is no "series_id" column in the created table. Of course, it shouldn't be any column with that name.

The correct column name the generation of FK should be looking for is "serie_id", and it does exist.

Now the strangest thing is that it doesn't fail for :local, for instance. It doesn't look for "locales_id", but "local_id" which is correct, and the corresponding FK is created.

I have custom Spanish inflections, and correct pluralizations are:

local -> locales

serie -> series

however I don't understand why the FK generation seems to be pluralizing in one case and not in the other.

I have found a working solution in this answer, which is declaring specifically the foreign keys, like:

add_foreign_key :turnos_registrados, :series, column: :serie_id

but what I'd like to know is why is this happening.

TylerH
  • 20,799
  • 66
  • 75
  • 101
user2553863
  • 682
  • 1
  • 8
  • 17

2 Answers2

3

It seems that Rails does this procedure to get the foreign key column name:

   referenced model in migration → (convert symbol to string) → pluralizesingularize

The pluralization is done when finding a table name for the given referenced model, see this line in the source code. Then, the singularization is done when deriving the actual foreign key column from the previously pluralized table name (see the source here).

However, in English, "series" is a collective noun, so the singular and plural forms are the same. Rails handles this correctly by default:

"series".pluralize
# => "series"

"series".singularize
# => "series"

But also note this:

"serie".pluralize
# => "series"

So, the "s" appendix is first added to the serie referenced model name and then the singularization yields the same word - "series". I.e. Rails yield the following for the :serie referenced model from your migration:

:serie → "serie" (conversion to string) → "series" (pluralize) → "series" (singularize)

So this is why Rails tries to look for series_id foreign key column but not for locales_id.

You write that you managed to add the correct key manually using add_foreign_key. I suspect that you will encounter more problems with your setup later as Rails will still try to derive the foreign key as series_id.

Either you'd have to specify the correct (serie_id) foreign_key everywhere in your associations or you should define a custom pluralization rule for your case. The rule should be added as an initializer that contains:

ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular 'serie', 'series'
end

Once you have this, the pluralization will work as expected for your particular case:

"series".singularize
# => "serie"

And with this custom rule I think that even your original migration should work without problems.

Matouš Borák
  • 15,606
  • 1
  • 42
  • 53
  • Thanks, BoraMa. Your answer is very detailed and you're right, even the migrations worked. I went on further and found another solution, I'll add it as an answer too. – user2553863 Apr 09 '16 at 17:11
1

Thanks to BoraMa's answer, I went further and found out the "why":

seems that some high-priority Rails default english inflections were getting in the way.

I have an inflection rule

inflect.singular(/((?<![aeiou][rndlj])e|a|i|o|u)s([A-Z]|_|$)/, '\1\2')

which should catch a

"series".singularize

since

2.2.1 :006 > "series".gsub(/((?<![aeiou][rndlj])e|a|i|o|u)s([A-Z]|_|$)/,'\1\2')
=> "serie" 

but it wasn't working. Somehow, adding the suggested

inflect.irregular 'serie', 'series'

made it work, but it should work without too. So, I suspected of some pre-defined rule, which can be confirmed (another BoraMa's contribution) in the console because the response to the sentence

ActiveSupport::Inflector.inflections.singulars

will print among others: [/(s)eries$/i, "\1eries"].

In order to get rid of these defaults, adding

  inflect.clear

to inflections.rb made it. Now it works with the more general rule I originally had.

Community
  • 1
  • 1
user2553863
  • 682
  • 1
  • 8
  • 17
  • Aah, I see, there is indeed a default singularization rule in Rails for "series" → "series". See for yourself in the console: `ActiveSupport::Inflector.inflections.singulars` will print among others: `[/(s)eries$/i, "\\1eries"]`. – Matouš Borák Apr 28 '16 at 14:22
  • 1
    Thanks again, you helped me to better understand how all this work. I will add it to the answer. – user2553863 Apr 30 '16 at 14:36