Ruby on Rails extending a gem's class and changing its inheriting class

1.2k Views Asked by At

I have a web app built on Ruby on Rails. In this app obviously use different Gems. Iv come to a point where i want to extend some specific gem's classes, and add methods to it.

Now i have a use case where i want to extend a gem's class, but instead of adding methods, i want to change its inherting class lets take the Impressionist Gem for example:

Iv created a new class in my app - app/models/impression.rb

class Impression < ActiveRecord::Base
  establish_connection(ENV[App_LOGS_DB'])
end

i want to change the Inheritance to use a my custom class called LogsBase

class Impression < LogsBase
end

the LogsBase class is definend as follows:

class LogsBase < ActiveRecord::Base
  establish_connection ENV['APP_LOGS_DB']
  self.abstract_class = true
end

When in try to run the server, the following exception is raised:

/gems/impressionist-1.6.1/lib/impressionist/models/active_record/impression.rb:5:in `<main>': superclass mismatch for class Impression (TypeError)

which from my understanding basically means there is a conflict between the gem's impression class definition and my own extention of that class.

Can anyone please help in finding out a way that i can change the Impression class inherting class, while still perserving the class's behaviour and making my server run properly?

PS: the goal of all this is to write the impressions data into a different database (logs db) rather than the main app db. in order to do that i need to establish a connection to the logs db, but if ill do it within the Impression class directly , it will blow up my pool of DB connections as indicated in the following link:

https://www.thegreatcodeadventure.com/managing-multiple-databases-in-a-single-rails-application/

thats why i need the abstract LogsBase class.

Any help will be appreciated.

2

There are 2 best solutions below

5
On

There is no (sensible) way to redefine a base class in ruby. It's possible, but only via weird hacks.

I would suggest taking one of two routes here:

  1. Fork the library, and make the base class configurable (with backwards compatibility).
  2. Don't do this via inheritance. Instead, put establish_connection ENV['SPLACER_LOGS_DB'] into a module, and include it in the class.

I would be inclined to use option 2 for now, as it's a quick/simple workaround that should fit well with the rest of the application.

0
On

Disclaimer: do not do that!

The only way I can think of is a nasty hack, neither reliable, nor robust, that is not compatible with newer versions of your external gem. Since Ruby does not allow to redefine the classes ancestors, you might (but please don’t):

  1. grab the content of original /gems/impressionist-1.6.1/lib/impressionist/models/active_record/impression.rb file.
  2. copy-paste it into your /blah/foo/impression.
  3. make your class be loading impressionist/models/active_record/impression explicitly.
  4. in the very second line of the file unset original class.

Something like this:

require 'impressionist/models/active_record/impression'

Object.send :remove_const, 'Impression'

class Impression < LogsBase
  # ORIGINAL CONTENT OF THIS FILE
end