0

I made a big work refactoring tonnes of code, and also made big changes to db schema. And now I am trying to write a rake task to migrate records from old tables to a new one.

I am having such classes:

#app/models/restream/service.rb
class Restream::Service < ActiveRecord::Base
def self.types
    %w(custom multiple_destinations_service one_destination_service) +
    Restream::Custom.types + Restream::MultipleDestinationsService.types
  end

  def self.find_sti_class(type_name) #allows to find classes by short names
    type_name = "Restream::#{type_name.camelcase}".constantize
    super
  end
end

#app/models/restream/custom.rb
class Restream::Custom < Restream::Service
  def self.sti_name
    "custom"
  end

  def self.types
    %w(periscope odnoklassniki vkontakte)
  end
end

#app/models/restream/periscope.rb
class Restream::Periscope < Restream::Custom
  def self.sti_name
    "periscope"
  end
end

Everything works just fine. Until I'm trying to add records manually. In my previous version I had such a structure:

class Restream::Custom < ActiveRecord::Base
  def self.types; %w(custom periscope vkontakte); end
end

class Restream::Periscope < Restream::Custom
  def self.sti_name; 'periscope'; end
end

And now I'm trying simply get all records from old restream_custom table and just copy type. Roughly:

Restream::Custom.create(type: old_restream_custom.type)

And that fails saying:

ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: periscope is not a subclass of Restream::Custom

It's obviously not! But anyway I already have a bunch of records with type: 'periscope', so that I know it's a valid value. What's the reason for this, and how can I fix this behaviour?

======

I can see two ways:

1) Set type to Restream::Periscope, not just a periscope. But that creates records, that can't be found by Restream::Periscope.find_each or Restream::Custom.find_each or smth like that, 'cause it will search for records with periscope in its type column, not a Restream::Periscope.

2) Select from restream_custom table only records with each types of custom, periscope, etc. and create Restream::Periscope for periscopes, not Restream::Custom and trying to provide a correct type here. But I found it kind of unpretty, not-DRY and unnecessary, and wonder if I can do smth more beautiful with it.

Ngoral
  • 4,215
  • 2
  • 20
  • 37

1 Answers1

-2

If it is not too big of a refactor, I would go with your first option 1) to set type to Restream::Periscope and not periscope primarily just because it is the Rails convention.

If option 1) is implemented, you said your other concern about this which is that Restream::Periscope.find_each will no longer return records of the other "types" and will be automatically filtered accordingly depending on the subclass... does makes sense because your .find_each is querying upon Restream::Periscope, and thus it is intuitive that I will be expecting that all of the returned records will be of type "Restream::Periscope".

Now if you'd like to query for "all types", then you may just query on the "parent" class which is the Restream::Service, in which you can now do the following:

Restream::Service.find_each(type: old_restream_custom.type)
# or
Restream::Service.create(type: old_restream_custom.type)

This is my suggestion, unless of course it is really a big task refactoring all of your code. Hope this somehow helps.

user229044
  • 232,980
  • 40
  • 330
  • 338
Jay-Ar Polidario
  • 6,463
  • 14
  • 28
  • I think I will end up with this. But the thing I really wanted to know is some... You know... "clever" thing. Something about how to load from class so that ActiveRecord can know that these are valid and does not raise the error. May be that's not a place where I should use it, but I'm really interested in it. – Ngoral Aug 31 '17 at 11:19
  • My guess is that Rails uses the stringified-constant i.e. `Restream::Periscope` as the value for `type`, because it also handles the namespace properly, that `Restream::Periscope` is distinct from say `AnotherNamespace::Periscope`. This is perhaps why Rails do not have a "magic" solution to identify `periscope` which as far as Rails is concerned, doesn't know yet what subclass this actually really is. Though, I am but just with my limited knowledge; perhaps I am just missing something as well. – Jay-Ar Polidario Aug 31 '17 at 11:29
  • Yes, I somehow understand why it is doing so. But using `self.sti_name` helps, while running rails app, but when running rake it ignores it for some reason.. – Ngoral Aug 31 '17 at 12:11
  • Ohhh I see, interesting... After you said that, I just found [this](https://stackoverflow.com/questions/23293177/rails-sti-how-to-change-mapping-between-class-name-value-of-the-type-column) which seems to be what you are also doing. I'll check on this later if I can reproduce your scenario. – Jay-Ar Polidario Aug 31 '17 at 12:18
  • The question you sent is exactly what I'm doing. I have all these `sti_name`, as you can see in my question. Now I realized that nothing really depends on this and I can use traditional typenames for STI, so removed `self.types` and `self.sti_names`, all tests passed successfully, but, oh God, when I running `Restream::Custom.find_by_sql("select * from restream_customs where type is not null").each ...` I got `The single-table inheritance mechanism failed to locate the subclass: 'periscope'.` Because records are already had these values in `type` col. So now I think how to manage with this... – Ngoral Sep 01 '17 at 10:11
  • yeah I was under the impression that you might have to update the `type` column values in the DB. This is also an alternative to your sql above (just in case it interests you), `Restream::Custom.where.not(type: nil)` – Jay-Ar Polidario Sep 01 '17 at 10:20
  • In fact, that's not the alternative. 'Cause it will run `SELECT * FROM restream_services WHERE type in ('Restream::Custom')`. And I need to select from `restream_customs` where it was stored before. Now I have several problems with retrieving parent id from database in polymorphic association.. Going on =) – Ngoral Sep 01 '17 at 12:11
  • Oh sorry yes, it should be `Restream::Service.where.not(type: nil)`. Good luck! :) Let me know if you need some help. – Jay-Ar Polidario Sep 01 '17 at 12:29
  • The code you suggest still will select from `restream_services` table while I need `restream_customs` =) – Ngoral Sep 02 '17 at 13:27
  • both `restream_services` and `restream_customs` are querying on just a single table because of STI, right? (the `services` table? and there's no `customs` table right?) By the way, how's it going? :) – Jay-Ar Polidario Sep 02 '17 at 22:47
  • There IS `restream_customs` table =) Because it was a self-standing class with its own table (there is code of old version in my question, and you can see `Restream::Customs < ActiveRecord::Base`). And now I need to migrate data from it to new STI table `restream_services`. I am doing OK, all migrated already =) – Ngoral Sep 03 '17 at 09:11
  • @Ngoral ohh I see! great job then! :) Feel free to send me a message directly anytime. Cheers! – Jay-Ar Polidario Sep 03 '17 at 16:11