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:
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))
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
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 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.
Related
In an application, I have a method that gets the name of a policy for a certain ActiveRecord as follows:
def policy_for(record:)
"#{record.class}Policy".constantize.new(record)
end
This works perfectly, except in one case where I have a model and a policy having the same name. This line then returns the model, and not the policy.
In other words, Problem: Multiple classes have the same name.
Goal: Need a constantize alternative that gets the class but in a certain directory, or has a certain superclass for example (BasePolicy instead of ActiveRecord).
How can I do this in Ruby?
1 - Don't have classes with similar names under the same module.
2 - In rails, models will be the outermost context. So consider moving your policies under a Policies namespace. That will solve your issue right and similar other issues down the road.
I'm using the globalize gem to translate the name attribute of my Color model. The gem seems to generate a Color::Translation ActiveRecord model, but it doesn't provide the file in app/models.
I want to add validations to this model, so I'm wondering if I can just create a file called app/models/color_translations.rb and do something like:
class Color::Translation < ActiveRecord::Base
validates_presence_of :name
end
would this extend the class's functionality (which is what I want) or overwrite everything (unwanted)?
This depends slightly on context. If there is a class Color::Translation then the result of loading your app/models/color_translations.rb file will be to add that validation. However if there is no such class, then it will define a new one.
The tricky thing in development is that classes are (in general) loaded on demand - you don't in general know what is already loaded versus what could be loaded. One way around this is to do
Color::Translation.class_eval do
validates_presence_of :name
end
which will never create a new class - it will use an existing one (if necessary Rails' autoloading will kick in) but if it can't find one it will raise an error.
The second problem you'll have is also related: if you stick this in a file in app/models how will rails know to load it if the class already exists?
It looks like globalizes creates these classes on the fly, so the safest place is to put this at the bottom of color.rb. This also ensures that if rails reloads Color and globalize thus creates a new Color::Translation that you validation will get added to this new class too.
How to load & get collection of all active-record models used inside rails app.
It should give classes from gems, plugins as well as subclasses having active-record base in parent hierarchy.
ActiveRecord::Base.descendants.collect(&:name)
gives me list but its only after all classes gets loaded.
Is there any way to load all classes inside rails app manually ?
The problem with Ruby is that "all classes" is a somewhat difficult thing to ascertain. Some of them may be generated dynamically and conditionally.
Sometimes you can just load what's present in app/models:
Dir.glob(File.expand_path("app/models/*.rb", Rails.root)).each do |model_file|
require model_file
end
If there's other locations that may contain models you'll need to include those, too.
You might have dependencies, though, and that can preclude model A from loading before model B. This is why the autoloader is used by default and things just aren't loaded in.
The only reliable way to get them all loaded is to somehow exercise them all at least once.
Getting model list based upon db tables -
ActiveRecord::Base.connection.tables.collect{|t| t.singularize.camelize.constantize rescue nil}.compact
I have a Rails app with a few model classes (e.g. Category, Subcategory, User, etc.). In order to implement a not-too-trivial filter functionality, I built a hierarchy of filter classes: FilterCategory, FilterSubcategory, etc., that derive from FilterBase. Each of them uses the appropriate "sister" model class (e.g. Category.find :all).
I quickly realized that I can't simply call the "sister" model class without using "require" first. However, I now suspect that using "require" is the main reason for two other problems I posted here and here, which probably mess up the class caching when config.cache_classes=false.
Is there another way for me to call these other models without requiring them?
I tried using the BaseWithoutTable plugin, but when I call the "sister model", I end up getting "Not a valid constant descriptor: nil", which occurs since Rails looks for "FilterCategory::Category" rather than "Category".
Any thoughts of the best way to do that?
I'm using Rails 2.3.8, Ruby 1.8.7.
Thanks,
Amit
I wonder if you want ::Category - getting Category from the top-level namespace rather than scoping it to FilterCategory?
If your models are in the app/models directory, you shouldn't need to explicitly require them - Rails already takes care of that.
I have some data that I want to store somewhere in my Rails app because I use it for generating form fields, checking a submitted form to ensure its values are valid, etc. Basically, I want the data in one location because I make use of it in several places.
Previously, I was defining an initialize method in my controller and initializing instance variables within that method, e.g. #graph_types = ['bar', 'line']. This seemed a bad idea because that's really all initialize was being used for (initializing those values) and the instance variables could be changed later, which I don't want.
Now, I define constants outside of any method in my controller, right up at the top after my filters, and I freeze them, e.g. GraphTypes = ['bar', 'line'].freeze.
I didn't want to store such data in a config file because then I would have to keep track of an extra file, read in the file and parse it, etc. I didn't want to store this data in the database because that seems like overkill; I don't need to do any crazy LEFT OUTER JOIN-type queries combining available graph types with another of my constants, say Themes = ['Keynote', 'Odeo', '37 Signals', 'Rails Keynote'].freeze. I didn't want to store the data in environment.rb because this data only pertains to a particular controller.
Considering all this, am I going about this 'the Ruby way'?
For constants that don't really belong anywhere else I have a StaticData class.
class StaticData
GRAPH_TYPES = ['bar', 'line']
SOMETHING_ELSE = ['A', 'B']
end
Then I get at it with
StaticData::GRAPH_TYPES
The same answer I wrote previously to a similar question applies and posting as this answer still comes up in search results.
Putting a constant in the controller makes some sense as the constant pertains directly to it. Constants should otherwise be put in the dedicated initializer file: Rails.root/config/initializers/constants.rb.
As per the comment listed in application.rb:
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded
This is still valid as of Rails 3.
I believe what you are currently doing is fine; you said the data only pertains to one controller, and therefore that's where it belongs. If it was needed for multiple controllers, or if they were more complex than constant values, other approaches may make sense.
Yes, what you are doing is fine. It's more idiomatic Ruby to call your constant GRAPH_TYPES though.
Incidentally, I would avoid defining initialize in your controllers. Seems like it could lead to trouble.
I would agree some what with IDBD and paradisepete. Using constants in the model would be the best way to go so that the controller is skinny and the model fat. see Rails view tips
For example if you had a metrics controller linked to a metric model. In the metric model
class Metric < ActiveRecord::Base
GRAPHTYPES = ['bar', 'line']
Then in the view you could do something like
f.select :graph_type, Metric::GRAPHTYPES
If you are generating forms that are related to some resource then it will be good variant to store it in the models. You don't need to store it in DB because it can be simple class or instance variables/methods.
The same idea is for validation. If you are validating resources/model instances then it will be reasonable choice to store validation parameters inside model class.
Anyways, it will be much closer to the 'thick model and thin controller' pattern then any of the variants you mentioned.