On Rails 3.2.6, I have a class that inherits from ActiveRecord::Base:
class Section < ActiveRecord::Base
...
end
When I inherit from this class, Rails will assume I want STI:
class AnotherSection < Section
..Rails assumes I have a type field, etc...
end
I want to be able to inherit from the Section class and use the subclass as a normal Ruby subclass, without the Rails STI magic.
Is there a way to prevent STI when subclassing from an ActiveRecord::Base model?
You can achieve this by disabling the inheritance_column for the model, like so:
class AnotherSection < Section
# disable STI
self.inheritance_column = :_type_disabled
end
The accepted answer will definitely work, but the recommended (dare I say "proper" :) way is to set abstract_class:
class Section < ActiveRecord::Base
self.abstract_class = true
end
The only fully supported strategy to store inheritance on ActiveRecord is STI. You can, however, simulate concrete class-table inheritance at your own risk. The concrete class-table inheritance with abstract superclass works fine, as pointed by smathy.
BUT ... If what you want is to make AnotherSection just an ordinary class (that will not be persisted at the database), you could disable the discriminator column (as suggested by Veraticus). However, if you save the AnotherSection it will be persisted in the same table as Section, and you will not be able to tell them apart. Also, if you use AnotherSection to find a Section, it will return an AnotherSection, breaking the original instantiation:
#create a Section and saves it
sect = Section.create()
sect.save()
#retrieve the Section as a AnotherSection, breaking polymorphism...
sect = AnotherSection.find(sect.id)
# another section is more than a section, it is inconsistent.
If AnotherSection is not intended to be persisted, the safest path it to override the persistence operations, such as save() and find():
class AnotherSection < Section
# disable STI, as pointed by Veraticus
self.inheritance_column = :_type_disabled
# disable save and finding
def save(*args)
#exception? do nothing?
end
def find(*args)
#exception? do nothing?
end
def find_by(*args)
#exception? do nothing?
end
# this does not stops here! there is first, last, and even a forty_two finder method! not to mention associations...
end
in a nutshell, you can do this, but you SHOULDN´T. The risk is high.
You should consider another option, such as using MIXIN instead of inheritance.
Related
I have an application I'm building where I need one model to create instances of another model. I want every Car to have 4 tires.
Car model
class Car < ActiveRecord::Base
has_many :tires
after_create :make_tires
def make_tires
4.times { Tire.create(car: self.id) }
end
end
Tire model
class Tire < ActiveRecord::Base
belongs_to :car
end
However, inside of make_tires there is an error that there is no activerecord method for create or new if I try it for Tire. When I inspect Tire it doesn't have those methods.
How can I remedy this?
The error is this: undefined method 'create' for ActiveRecord::AttributeMethods::Serialization::Tire::Module
I have tested two environments: Testing and Development and they both fail for the same error.
It is a name conflict. Sit down and relax while I explain.
Solution with explanation:
In Ruby classes are just instances of class Class (which is a subclass of class Module). Instances of Module (including instances of Class) are quite weird objects, especially weird is their connection with ruby constants. You can create a new class at any point using standard ruby notation:
my_class = Class.new { attr_accessor :a }
instance = my_class.new
instance.a = 3
insatnce.a #=>
instance.class.name #=> nil
Well, our class has no name. It is just an anonymous class. How do classes get their name? By assigning it to a constant (for the first time):
MyClass = my_class
my_class.name #=> 'MyClass'
When you define class using a class keyword:
class MyClass
...
end
You just create a new instance of Class and assign it to a constant. Because of that, Ruby compiler seeing a constant has no idea whether it is a class or a number under it - it has to make a full search for that constant.
The logic behind finding a constant is quite complex and depends on the current nesting. Your case is quite simple (as there is no nesting), so ruby will try to find Tire class inside your class first and when failed it's subclasses and included modules.
Your problem is that your class inherits from ActiveRecord::Base (which is correct), which includes ActiveRecord::AttributeMethods::Serialization module, which defines Tire constant already. Hence, ruby will use this constant instead, as this is the best match for that name in given context.
To fix it, you must tell the compiler not to look within the current class but directly in the "top namespace" (which in ruby is Object. Seriously, try Object.constants) - you can do that using :: in front of your constant, like ::Tire.
Note: even though it works, this issue is a first warning for you that your code starts to smell. You should look after this ActiveRecord::AttributeMethods::Serialization::Tire::Module thingy as it seems you will encounter it more than once in the future.
Other stuff:
You can simplify your method slightly:
def make_tires
4.times { tires.create }
end
At that point you might encounter some error you had initially. If you do, then please find what is going on with that Tire::Module thing. If you don't care about the smell:
has_many :tires, class_name: '::Tire'
I'm not sure what's causing the exception you are seeing but you have a number of issues. First, you need to pass in a car instance instead of the id in make_tires. Like this:
def make_tires
4.times { Tire.create(car: self) }
end
You also need to have attr_accessible :car in the Tire model. Like this:
class Tire < ActiveRecord::Base
belongs_to :car
attr_accessible :car
end
some of my models has a "company_id" column, that I want to set automatically. So I thought to override some method in activerecord base.
I tried this, in config/initializers, but does not work:
class ActiveRecord::Base
after_initialize :init
def init
if (self.respond_to(:company_id))
self.company_id= UserSession.find.user.company_id
end
end
end
Solution after Simone Carletti answer:
I created a module:
module WithCompany
def initialize_company
self.company_id= UserSession.find.user.company_id
end
end
And included this in the model:
class Exam < ActiveRecord::Base
include WithCompany
after_initialize :init
def init
initialize_company
end
end
Is there something else that I can do?
update 2
Best practices says to do not set session related fields in models. Use controllers for that.
There are two problems here. The first, is that you are injecting a bunch of stuff into all ActiveRecord models, whereas it would be better to add the feature only to the relevant models.
Secondary, you are breaking the MVC pattern trying to inject into the model the session context.
What you should do instead, is to code your feature in a module, and mix the module only in the relevant models. As per the context, rather than overriding the default AR behavior, add a new method where you pass the current session context (dependency injection) and returns the model initialized with the required company, when the session is set properly and the model is company-aware.
I have the following HABTM relation and corresponding logic that is identical in at least 3 different tables, so I wanted to factor it, and related methods, into a common base class. The problem is, even though the association does show up in the object's association_cache, trying to access it results in an exception (ArgumentError comparison of nil object with a string). I'm confused about why this isn't working, as this seems like basic OOP programming.
My base model is set up like this:
class ProfileItem < ActiveRecord::Base
self.abstract_class = true
has_and_belongs_to_many :profiles
attr_accessor :profile_ids
before_destroy :clean_up
before_save :update_profiles
def get_profile_names
self.profiles.each do |p| # << exception here
...
Look into a concept called concerns, introduced in Rails 4. Abstracting a class inherited from ActiveRecord::Base is a recipe for trouble.
A good explanation How to use concerns in Rails 4.
I want to use a column called type without invoking Single Table Inheritance (STI) - I just want type to be a normal column that holds a String.
How can I do this without having Rails expecting me to have single table inheritance and throwing an exception of The single-table inheritance mechanism failed to locate the subclass...This error is raised because the column 'type' is reserved for storing the class in case of inheritance.?
Any ideas on how to do this?
In Rails 3.1 set_inheritance_column is deprecated, also you can just use nil as a name, like this:
class Pancakes < ActiveRecord::Base
self.inheritance_column = nil
#...
end
You can override the STI column name using set_inheritance_column:
class Pancakes < ActiveRecord::Base
set_inheritance_column 'something_you_will_not_use'
#...
end
So pick some column name that you won't use for anything and feed that to set_inheritance_column.
In newer versions of Rails you'd set inheritance_column to nil:
class Pancakes < ActiveRecord::Base
self.inheritance_column = nil
#...
end
I know this question is rather old and this deviates a bit from the question you are asking, but what I always do whenever I feel the urge to name a column type or something_type is I search for a synonym of type and use that instead:
Here are a couple alternatives: kind, sort, variety, category, set, genre, species, order etc.
Rails 4.x
I encountered the problem in a Rails 4 app, but in Rails 4 the set_inheritance_column method does not exist at all so you can't use it.
The solution that worked for me was to disable the single table inheritance by overriding ActiveRecord’s inheritance_column method, like this:
class MyModel < ActiveRecord::Base
private
def self.inheritance_column
nil
end
end
Hope it helps!
If you want to do this for all models, you can stick this in an initializer.
ActiveSupport.on_load(:active_record) do
class ::ActiveRecord::Base
# disable STI to allow columns named "type"
self.inheritance_column = :_type_disabled
end
end
Class A and B are identical:
class A < ActiveRecord::Base
def foo
puts "foo"
end
end
class B < ActiveRecord::Base
def foo
puts "foo"
end
end
What's the difference between refactoring like this with a base class:
class Base < ActiveRecord::Base
def foo
puts "foo"
end
end
class A < Base
end
class B < Base
end
versus like this using a base module:
module Base
def foo
puts "foo"
end
end
class A < ActiveRecord::Base
include Base
end
class B < ActiveRecord::Base
include Base
end
Is one way preferable over another?
There's a fundamental difference between those two methods that all the other answers are missing, and that's rails' implementation of STIs (Single Table Inheritance):
http://api.rubyonrails.org/classes/ActiveRecord/Base.html (Find the "Single Table Inheritance" section)
Basically, if you refactor your Base class like this:
class Base < ActiveRecord::Base
def foo
puts "foo"
end
end
class A < Base
end
class B < Base
end
Then, you are supposed to have a database table called "bases", with a column called "type", which should have a value of "A" or "B". The columns on this table will be the same across all your models, and if you have a column that belongs to only one of the models, your "bases" table will be denormalized.
Whereas, if you refactor your Base class like this:
Module Base
def foo
puts "foo"
end
end
class A < ActiveRecord::Base
include Base
end
class B < ActiveRecord::Base
include Base
end
Then there will be no table "bases". Instead, there will be a table "as" and a table "bs". If they have the same attributes, the columns will have to be duplicated across both tables, but if there are differences, they won't be denomarlized.
So, if one is preferable over the other, yes, but that's specific to your application. As a rule of thumb, if they have the exact same properties or a big overlap, use STI (1st example), else, use Modules (2nd example).
Both of these methods will work. When deciding to use a module or a class, the question I have is does the class fit into the object hierarchy, or are these just methods I am looking to reuse. If I am just trying to factor out common code for DRY reasons, that sounds like a module. If there really is a class that fits into the hierarchy that makes sense on its own, I use a class.
Coming from a Java background, it is refreshing I can choose to make these decisions.
You have more flexibility with the module. The module's intent is to span across different types of classes. With the other method you are locking yourself into Base. Other than that, there isn't much difference.
Ruby's answer to multiple inheritance is mixins. Since your classes are already inheriting from Rails specific classes, they can no longer inherit from your custom classes.
So your choice is to chain together in a long chain, or use a mixin which is much cleaner, and easier to understand.
The module gives you more flexibility in that 1) you can only inherit from one class, but you can include multiple modules, and 2) you can't inherit from a base class without inheriting its superclasses, but you can include a module all by itself (e.g. you might want to add the "foo" method to another class that isn't an active record model).
Another difference is that within the methods in the class Base you could call things from ActiveRecord::Base, but you couldn't do that from the module.
It depends on what you are really trying to do.
Overriding or adding methods to ActiveRecord::Base: Do this if you want every ActiveRecord model in your app to respond_to foo.
Subclass ActiveRecord::Base, and have every model inherit from your subclass: Achieves the same as 1, but every model in your app needs to extend an unconventional class, so why go through the trouble.
include module: This works great if only some number of models need access to foo. This is pretty much what all those acts_as_<whatever> plugins do.
Bottom line, if you want every single model to have a different behavior to what ActiveRecord::Base already provides, use option 1. If only a handful of your models require the behavior, create a module and include it in your models (option 3).