Search code examples
ruby-on-rails-4has-manypolymorphic-associationslegacy

Rails 4 polymorphic has_many ignores table_name


Short version: I'm building a new Rails 4 application that uses (read-only) some tables from a database used by a legacy Rails 2 application, which is still in use. The old application models/tables were very confusingly named, however (especially in the context of the new application), so I want to use different names for the models/tables using self.table_name. This all works perfectly until I tried to add in a polymorphic relationship. Rails ignores my defined table_name and does a query on the type using the new model name, which of course is different so it doesn't work. Is there any way to change this?

Long version: There are three models in this equation, and here they are:

class Exporter < MysqlBase
  has_many :lic_exporter_addresses, :as => :place

  self.table_name = 'excons'
  self.primary_key = 'id'
end

class LicBusiness < MysqlBase
  has_one :physical_address, -> { where(category: 'Physical') }, :class_name => 'LicExporterAddress', :as => :place
  has_one :mailing_address, -> { where(category: 'Mailing') }, :class_name => 'LicExporterAddress', :as => :place
  has_many :lic_exporter_addresses, :as => :place

  self.table_name = 'businesses'
  self.primary_key = 'id'
end

class LicExporterAddress < MysqlBase
  belongs_to :place, polymorphic: true

  self.table_name = 'addresses'
  self.primary_key = 'id'
end

We have a ton of different kinds of businesses, so the Business model is the most problematic. I really don't want to have that in the new app because it would be very confusing as to what a "business" actually is. With the current code if I go into the rails console and try to get lic_exporter_addresses for a LicBusiness or Exporter, it does:

 SELECT `addresses`.* FROM `addresses` WHERE `addresses`.`place_id` = '00044c693f6848f9b0978f873cf9999a' AND `addresses`.`place_type` = 'LicBusiness'

when what I need is place_type = 'Business'.

Is there any way to tell Rails what place_type to look for? I did see this question and the second answer looked promising, except that I'm already sort of doing that with Physical and Mailing addresses so I can't figure out how that'd work with both options at the same time... Thanks for any info or ideas.


Solution

  • In Rails 4.2, it looks like the exact string used for the query is defined as owner.class.base_class.name, where owner is the model declaring the association. So I don't think it's directly supported. But there are a few ways I can think of to hack around this. I think the most promising might be, in LicBusiness:

    has_many :lic_exporter_addresses, ->{where place_type: "Business"}, foreign_key: "place_id"
    

    That is, don't define the association as polymorphic, but define the type scope yourself. This will NOT correctly define place_type in the lic_exporter_addresses table if you ever use lic_exporter_address.place = some_lic_business_instance. However you said this table was read-only, so this may in fact not be an issue for you. If it is, there may be ways to override the behavior to get what you need.

    Two other ideas both make me very nervous and I think they are probably quite dangerous for unintended side-effects. They are to override LicBusiness.base_class (this might actually be ok if you do not now and never will have STI set up on LicBusiness, but I'm still nervous), or to override LicBusiness.name (I'm pretty sure this would have unintended side-effects).