简体   繁体   中英

Rails Single Table Inheritance with dynamically defined classes

In using Rails STI (Single Table Inheritance), I have defined a model named Poi (point of interest).

Our app's requirement dictate that subclasses of Poi (like Restaurant, Club, etc) must be created in an Admin::Categories view (where it has a class_name string input field), so that an Admin should be able to create a new subclass at anytime without needing a programmer to open a new ruby file with an empty (useless) subclass and re-deploy the app.

At the same time, IF in the future we want to specify different behavior (either instance/class methods) for a subclass of Poi, we can just create that ruby file, but that should be an option and not mandatory. The same goes for a different form with different fields for that subclass: we just need to setup a partial_name_for_form instance method in that subclass that returns a string with the partial name and the views will render that accordingly. If none is found, the default Poi views are rendered.

Since Rails raises an error if you try to instanciate a new Poi object with 'type' attribute that doesn't match a subclass of Poi (so the subclass MUST be previously defined), we came up with the following solution for dynamically creating the Poi subclasses based on class_name:

  1. An after_create hook in model Category that defines the new class immediately using this code: Object.const_set(category.class_name, Class.new(Poi))

  2. A require_dependency call in model Poi file (because it's in the autoload path) to require the ruby files for the subclasses we have eventually created hardcoded subclasses (only if the file exists):

Category.all.each do |category|

require_dependency category.class_name.underscore if File.exist (File.join("app","models","pois","#{category.class_name.underscore}.rb"))

end

  1. An initializer that defines all remaining classes (by 'remaining' I mean other subclasses that don't still have their own ruby file defining them) using the same code in #1, but checking if Object.const_defined? category.class_name if Object.const_defined? category.class_name first (because the ones defined by require_dependency don't need to be re-defined).

Even tough all this complexity made we almost regret using STI in the first place, it was working fine - in development .

But in production environment, after the creation of a new category providing class_name , the Class doesn't get defined, because when trying to create a new Poi with that subclass is raising an error uninitialized constant .

I confirmed in Rails console in Production environment that the after_create hook IS working, because the class is being defined there. My wild guess is that because we use Unicorn, this bug could be related to the forking of the application code, but I have no clue how to proceed.

10.5 require_dependency and Initializers

One could think about doing some require_dependency calls in an initializer to make sure certain constants are loaded upfront, for example as an attempt to address the gotcha with STIs.

Problem is, in development mode autoloaded constants are wiped if there is any relevant change in the file system. If that happens then we are in the very same situation the initializer wanted to avoid!

From http://guides.rubyonrails.org/autoloading_and_reloading_constants.html . From what I read here, it seems require_dependency in initializer works differently between environments. I found a similar question here - see if that helps.

I would avoid creating dynamic constants because of a broken model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM