Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Rails has_many association does not support irregular inflection #8928

Closed
bluebird-communication opened this Issue Jan 14, 2013 · 11 comments

Comments

Projects
None yet
5 participants

NameError in History/hist_paquets_mesures#show
Showing /var/www-opf/opf/app/views/history/hist_paquets_mesures/show.html.haml where line #19 raised:
uninitialized constant HistPaquetMesures::HistVersionsPaquetsMesure

The part of my (HAML) view causing this issue :

= debug @hist_paquet_mesures.hist_versions_paquets_mesures

I set up an exception for the special singular-plural names that I use in config/initializers/inflections.rb :

  ActiveSupport::Inflector.inflections do |inflect|
    inflect.irregular 'pub_liste_horizon', 'pub_listes_horizon'
    inflect.irregular 'hist_paquet_mesures', 'hist_paquets_mesures'
    inflect.irregular 'hist_projet_connexe', 'hist_projets_connexes'
    inflect.irregular 'hist_version_paquet_mesures', 'hist_versions_paquets_mesures'
    inflect.irregular 'hist_origine_modification', 'hist_origines_modification'
  end

Pluralize , singularize and classify methods are working as expected on rails console :

"hist_versions_paquets_mesures".singularize  => "hist_version_paquet_mesures"
"hist_versions_paquets_mesures".classify  => "HistVersionPaquetMesures"
"hist_version_paquet_mesures".pluralize  => "hist_versions_paquets_mesures"

My app/model/hist_paquet_mesures.rb model :

class HistPaquetMesures < ActiveRecord::Base
  belongs_to :pub_indice
  belongs_to :pub_liste_horizon
  belongs_to :admin_utilisateur
  has_many :hist_versions_paquets_mesures
end

My app/model/hist_version_paquet_mesures.rb model :

class HistVersionPaquetMesures < ActiveRecord::Base
  belongs_to :hist_paquet_mesures
  belongs_to :pub_modification
  belongs_to :vers_origine, :class_name => 'HistOrigineModification', :foreign_key => 'hist_origine_modification_id'
  # polymorphic association
  has_one :comme_origine, :class_name => 'HistOrigineModification', as: :hist_origine
end

My app/controllers/history/hist_paquets_mesures_controller.rb controller :

class History::HistPaquetsMesuresController < ApplicationController
  def show
    @hist_paquet_mesures = HistPaquetMesures.find_by_id(params[:id])
    respond_with(:history, @hist_paquet_mesures)
  end
end

The weird thing is where the "s" go in "HistVersionsPaquetsMesure" of the error message : HistPaquetMesures::HistVersionsPaquetsMesure

Other weird things is that the association is working in the opposite direction (belongs_to) (i.e. : my_hist_version_paquet_mesures.hist_paquet_mesures)

Shouldn't it be HistPaquetMesures::HistVersionPaquetMesures ?
Why do I have this result ?

Member

senny commented Jan 14, 2013

This is related to the inflector. To transform the association name into a class AR uses camelize.singularize. In your case this translates to "hist_versions_paquets_mesures".camelize.singularize # => "HistVersionsPaquetsMesure", which explains the error. If AR would perform the operations the other way around it would work: "hist_versions_paquets_mesures".singularize.camelize # => "HistVersionPaquetMesures".

The root of the problem is that the inflector behavior varies if you define your inflections camelcased or underscored. I'm not if this is actually expected behavior but the inflector code is generally frozen.

@fxn could you clarify?

Thanks for your quick answer.
Do you think it will cover any unidentified issue if I add as well the camelized irregular inflection ?

For now, as suggested here the workaround is to add the classname to the association :
has_many :hist_versions_paquets_mesures, :classname => HistVersionPaquetMesures

Member

senny commented Jan 14, 2013

adding the :class_name option will solve this specific issue the problem in the inflector will still be present though. I guess I would add the camelized inflection too but I'm not sure if this leads to different problems. Let me know if you try it.

I tried with only the camelized inflection (without :class_name option) => it did work
I tried with only the :class_name option (without camelized inflection) => it did work
I tried with both camelized inflection and :class_name option => it did work too
There maybe side effects, but I couldn't detect any

If it can help to decide to apply a fix, I checked the classify method in ActiveSupport::Inflector

The logic is to singularize first, and then camelize :

# Create a class name from a plural table name like Rails does for table names to models.
# Note that this returns a string and not a Class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
# Examples:
# "egg_and_hams".classify # => "EggAndHam"
# "posts".classify # => "Post"
#
# Singular names are not handled correctly:
# "business".classify # => "Busines"
def classify(table_name)
  # strip out any leading schema name
  camelize(singularize(table_name.to_s.sub(/.*\./, '')))
end

If we follow the same logic. The issue is more likely to come from ActiveRecord, according to my trace :

activerecord (3.2.11) lib/active_record/inheritance.rb:111:in `compute_type'
activerecord (3.2.11) lib/active_record/reflection.rb:172:in `klass'
activerecord (3.2.11) lib/active_record/associations/association.rb:117:in `klass'
activerecord (3.2.11) lib/active_record/associations/association.rb:123:in `target_scope'
activerecord (3.2.11) lib/active_record/associations/association.rb:87:in `scoped'

compute_type method is melting my brain, but seems to follow a different logic from classify

Contributor

al2o3cr commented Jan 14, 2013

I've run into this issue before, and it was pretty annoying - especially since the inflection works the other way (singular camelCase class name => plural underscored table name).

I ended up resolving it by monkey-patching derive_class_name on AssociationReflection (link) to this:

  def derive_class_name
    class_name = name.to_s
    class_name = class_name.singularize if collection?
    class_name = class_name.camelize
    class_name
  end

This accurately matches both classify (cited above) and reverses the calculation in table_name which is underscore-then-pluralize.

It may be valuable to fix this - it's likely to be entirely harmless, as anybody who has an association that would be affected is either hitting the bug or has worked around it with :class_name already.

For those facing this issue.
It is easier to make the workaround by adding the singluar/plural camelized form into the config/initializers/inflections.rb file, than adding a :class_name => YourSingularModel option into each models using the association facing this bug.

Contributor

laurocaetano commented Feb 12, 2014

Is there anything to be done here or is it ok to go with @bluebird-communication's suggestion?

Member

senny commented Feb 12, 2014

@laurocaetano the behavior described in my previous comment is odd. I'd like to hear from @fxn before closing.

There are reasonable ways to avoid the issue so we are not pressured here.

Contributor

laurocaetano commented Feb 12, 2014

@senny sure! Thanks :)

ekampp commented Apr 2, 2014

I have fixed this by explicitly overriding the plural version of the falsely interpreted inflection. In my case, I have a character that has_many :wealth, which translates to wealths, which is wrong.

# inflections.rb
inflect.plural /^(wealth)$/i, '\1'

arthurnn added a commit to arthurnn/rails that referenced this issue Apr 18, 2014

Regression test for irregular inflection on has_many
Also add a Changelog entry

[related #9702]
[fixes #8928]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment