How to customize Rails 3 STI column name - ruby-on-rails

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.

Related

Ruby on rails active record queries which one is efficient

I was recently working on a project where I faced a dilemma of choosing between two ways of getting same results. Here is the class structure:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
An author has first name, last name. I want to get the full name of the author for a given book as an instance method.
In simple active record terms, since book is associated with author, we can get the author name for a book as follows:
For example in Book class, we have:
class Book < ApplicationRecord
belongs_to :author
def author_name
"#{author.first_name} #{author.last_name}"
end
end
And we get the result!
But, according to the target of minimizing dependencies (POODR Book), future ease of change and better object oriented design, the book should not know properties of an author. It should interact with an author object by interfaces.
So Book should not be the one responsible for getting the Author name. The author class should.
class Book < ApplicationRecord
belongs_to :author
def author_name
get_author_name(self.author_id)
end
private
#minimizing class dependecies by providing private methods as external interfaces
def get_author_name(author_id)
Author.get_author_name_from_id(author_id)
end
end
class Author < ApplicationRecord
has_many :books
#class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling.
def self.get_author_name_from_id(id)
author = self.find_by_id(id)
author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
Now, book is just interacting with the public interface provided by Author and Author is handling the responsibility of getting full name from its properties which is a better design for sure.
I tried running the queries as two separate methods in my console:
class Book < ApplicationRecord
def author_name
get_author_name(self.author_id)
end
def author_name2
"#{author.last_name} + #{author.first_name}"
end
end
The results are shown below:
Looks like both run the same queries.
My questions are
Does rails convert author.last_name called inside the Book class to
the same SQL query as Author.find_by_id(author_id).last_name called inside
Author class (through message passing from Book class) in case of bigger data size?
Which one is more performant in case of bigger data size?
Doesn't calling author.last_name from Book class violates design
principles ?
It's actually much more common and simplier to use delegation.
class Book < ApplicationRecord
belongs_to :author
delegate :name, to: :author, prefix: true, allow_nil: true
end
class Author < ApplicationRecord
has_many :books
def name
"#{first_name.titleize} #(last_name.titleize}"
end
end
As to performance, if you join the authors at the time of the book query you end up doing a single query.
#books = Book.joins(:author)
Now when you iterate through #books and you call individually book.author_name no SQL query needs to be made to the authors table.
1) Obviously not, it performs JOIN of books & authors tables. What you've made requires 2 queries, instead of 1 join you'll have book.find(id) and author.find(book.author_id).
2) JOIN should be faster.
3) Since last_name is a public interface, it absolutely doesn't violate design principles. It would violate principles if you were accessing author's last name from outside like that: Book.find(1).author.last_name - that's a bad thing. Correct is: Book.find(1).authors_last_name - and accessing author's name inside Model class.
Your provided example seems to be overcomplicated to me.
According to the example you shared, you only want to get full name of the book's author. So, the idea of splitting responsibility is correct, but in Author class should be simple instance method full_name, like:
class Author < ApplicationRecord
has_many :books
def full_name
"#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
class Book < ActiveRecord::Base
belongs_to :author
def author_name
author.full_name
end
end
Note, there're no direct queries in this code. Once you'll need the author's name somewhere (in a view, in api response, etc), Rails will make the most optimized query possible (depends on your use case though, it may be ineffective for example, if you call iterate over books and call author in a loop)
I prefer the second approach because the full_name is property of author not a book. If the book wants to access that information, it can using book.author&.full_name (& is for handling cases of books with no authors).
but I would suggest a refactoring as below:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
def full_name
"#{firstname} #{lastname}"
end
end
Does rails convert author.last_name called inside the Book class to the same SQL query as Author.find_by_id(author_id).last_name called inside Author class (through message passing from Book class) in case of bigger data size?
Depend upon the calling factor, like in your example both will generate the same query. But if you have a include\join clause while getting the Book/Author, both will generate different queries.
As per the rails convention, Author.find_by_id(author_id).last_name is not recommended as it will always fire a query on database whenever the method is called. One should use the rails' association interface to call the method on related object which is smart to identify the object from memory or fetch it from database if not in memory.
Which one is more performant in case of bigger data size?
author.last_name is better because it will take care of joins, include, and memoization clauses if used and avoid the N+1 query problem.
Doesn't calling author.last_name from Book class violates design principles?
No, you can even use delegate like #Steve Suggested.
In my experience, it's a balancing act between minimizing code complexity and minimizing scalability issues.
However, in this case, I think the simplest solution that would separate class concerns and minimize code would be to simply use: #book.author.full_name
And in your Author.rb define full_name in Author.rb:
def full_name
"#{self.first_name} #{self.last_name}"
end
This will simplify your code a lot. For example, if in the future you had another model called Magazine that has an Author, you don't have to go define author_name in the Magazine model as well. You simply use #magazine.author.full_name. This will DRY up your code nicely.

Calling models with _ us controllers

I have a table in the model called pg_search_documents, how do I work with it in the controllers?
I'm trying like this:
def show
#search = PgSearchDocument.find(params[:content])
end
But the so-called "PgSearchDocument" seems to be wrong.
You need to make sure you have a model declared in your app. If you have not done so, create the following file:
app/models/pg_search_document.rb
class PgSearchDocument < ActiveRecord::Base
end
In Rails 5 you would use:
class PgSearchDocument < ApplicationRecord
end
Please note the following naming conventions in Rails:
Database table name is plural snake case: pg_search_documents
Model filename is singular snake case: pg_search_document.rb
Model class name is singular camel case: PgSearchDocument

Rails -- use type column without STI?

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

Rails: dynamic environmental settings without magic numbers

Short version: Where should I store environment-specific IDs? ENV['some-variable']? Somewhere else?
Long version:
Let's say I have a model called Books and a book has a Category. (For the sake of this question, let's say a book only has one category.)
class Book < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :books
end
Now let's say one category is called 'erotica.' And I want to suppress erotica books in my type ahead. That seems straight forward. But in production and in development 'erotica' has a different ID. I don't want my code to be ID dependent. I don't want it to be string dependent (in case 'erotica' is renamed pr0n or whatever).
I think I should have something like
def suppress_method
suppress_category_id = look_up_suppression_id
...
end
but where should 'look up' look?
Thanks!
Taking this approach will be brittle, what if you want to suppress multiple categories? Erotica and Politics? The best design here is for you to actually add 'suppressed' as a boolean to category in a migration, and maintain that in your application's administration interface. After you've done that you can add a named scope like:
class Category < ActiveRecord::Base
named_scope :not_suppressed, :conditions=>{:suppressed=>false}
# or for rails 3
scope :not_suppressed, where(:suppressed=>false)
end
Then just update your type ahead code to do:
Category.not_suppressed.find ...
Rather than
Category.find

Why MyModel.all works in Rails?

i don't understand this little thing:
Suppose, we have "Condition" model
class Condition < ActiveRecord::Base
end
Why Condition.all works ?
Condition.all.each { |p| do_something }
This syntax tells us, that we have "Condition" class-object instanciated somewhere ?
Or is it some convention over configuration case ?
I asking this, because i want to override Condition.all method to return Conditions, sorted by "created_at" field value ?
I don't need to use sort method in place, i want to insert Conditions to, because in the entire project i need only one sorting
Thanks
Person.all is just an alias for Person.find(:all) (see the documentation here).
all, like find, is a class method on ActiveRecord::Base so doesn't require an instance in order to be called.
Update
To override a class method you need to remember the self. prefix. e.g. you can override all like this:
class Condition < ActiveRecord::Base
def self.all(*args)
# overridden implementation here
end
end
If you aren't clear on instance methods vs. class methods read this blog post which is a good summary,
However, if you just want to specify a default ordering you don't need to do this. You can just use default_scope:
class Condition < ActiveRecord::Base
default_scope :order => 'created_at'
end

Resources