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
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
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.
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.
I have a class named Post:
class Post < ActiveRecord::Base
end
I have a class named Question that inheriting from Post:
class Question < Post
end
and I have a class named Answer that also inheriting from 'Post':
class Answer < Post
end
In Post, I have a column named post_type_id and its' type is Integer.
How do I use STI and specific column name & type to inherit from Post? 0 means Question and 1 means Answer. (0 & 1 is the value of post_type_id in posts table)
You can change the name of the single table inheritance column like so:
class Post < ActiveRecord::Base
self.inheritance_column = 'type_column_name'
end
However, there is no way to cause Rails to use integers instead of storing the actual type as a string, which makes me think that this may not be a great use case for single target inheritance. Perhaps a scope would suit you better instead:
class Post < ActiveRecord::Base
scope :questions, where(:post_type_id => 0)
scope :answers, where(:post_type_id => 1)
end
#questions = Post.questions.all
#answers = Post.answers.all
There actually is a way, as with all things. You can override the find_sti_class method to look for your type based on an integer.
This might be what you're looking for.
http://lorefnon.me/2014/07/27/optimizing-sti-columns.html
EDIT: Updated URL (thanks #SubhashChandran)
You will need to set your column type using self.inheritance_column
Then you will have to provide your own behavior for the following method: "sti_class_for(type_name)" which is located in this file "activerecord/lib/active_record/inheritance.rb"
so you will have to monkey patch the above file.
This is not the recommended way, you will have to make a migration and change all the values to match your class name.
I'm using a tool(UltraSms) that required three tables named (smsin, smsout & smsparts)
I need these tables to be used in the same Rails application that has other tables. With ActiveRecrod I know that table names has to be plural of the Active record class name by convention. Is there a way to map those to an ActiveRecrod class easily or should I find manual way to do ORM for it?
Thanks,
Tam
Seems that in Rails3.1 , the method name changed to table_name=, e.g.
class Mouse < ActiveRecord::Base
self.table_name = "mice"
end
You can do this:
class MyClass < ActiveRecord::Base
set_table_name "smsin"
end