Wrong sql request done with a "has_many" for a subclass - ruby-on-rails

I'm trying to implement a "facebook like" like relationship.
I wanted to use inheritance for that, so here's my structure :
Likes with a user_id:integer attribute
|> PostsLike that inherits Like with a post_id:integer attribute
So here's my Like.rb model :
class Like < ActiveRecord::Base
belongs_to :user
end
And then my PostLike.rb model :
class Postlike < Like
belongs_to :post
end
And finally I have a post model that will have multiple postlikes objects :
class Post < ActiveRecord::Base
has_many :postlikes
end
But here's my problem, when I go into irc and that :
I get a Post object :
ruby-1.9.2-p290 :001 > #p = Post.all.first
I try to get postlikes of this object, here's the sql statement :
SELECT likes.* FROM likes WHERE likes.type IN ('Postlike') AND likes.post_id = 310
So basically instead of doing
postlikes.post_id
Rails' doing
likes.post_id
Any idea to fix that ?

ActiveRecord stores Like and Postlike object in the same table named "likes", and uses a column named "type" to save the class of the object. That explains the statement:
WHERE likes.type IN ('Postlike') AND likes.post_id = 310
There is no table postlikes
See "Single table inheritance" chapter at http://api.rubyonrails.org/classes/ActiveRecord/Base.html

Related

Rails3: Use joined table's data

I'm trying to access my joined table data but alas..
For example association between authors and books, author have many books:
I'm using join funtion to join the author table to the books table in order to access the author's author_name attribute
class Author < ActiveRecord::Base
has_many :books
attr_accessible :author_name
end
class Book < ActiveRecord::Base
belongs_to :author
end
random_author = Author.first
books = random_author.books.where(id > 5).joins(:author) #this should make all author attributes available to each book, right?
book_1 = books.first
book_1.author_name
=> NoMethodError: undefined method `author_name' for #<Book:0x111111>
Of course using the association would work: book_1.author.author_name but that will require another query, which is what I'm trying to avoid.
I mean the joins operation joins the author's data- there must be a way to access it right?
p.s. I can use includes method. and eager load the author data as well, but since I'm just needing a single attribute- is there a way to accomplished it with only a joins method? Thank you
You need to add
.select('books.*, author.name AS author_name')
So your query becomes
books = random_author.books.where(id > 5).joins(:author).select('books.*, author.name AS author_name')

Rails includes with where

I'm trying to eager load some associations, but I want to do filtering on some of the sub-relations here is what I mean.
class MyModel < ActiveRecord::Base
has_many :my_model_details, dependent: :destroy
end
class MyModelDetails < ActiveRecord::Base
belongs_to :property
belongs_to :my_model
belongs_to :track
belongs_to :person
end
So if I want to get all MyModel objects which have details that belong to certain property with property name I would do this.
MyModel.includes(my_model_details: [:property, :person]).where('property.property_name = ?', 'Property name')
The reason why I want use includes instead of joins, is that I want to have my model details in grouped by the last property and person name. So I don't want to fire the N+1 query in that case.
Ex
If my query returns single MyModel object with two MyModelDetail records.
Where mymodeldetail id 1 has property id 1 and mymodeldetail id2 has property id 2. And they both belong to the same person.
So on my UI I would display this:
MyModel 1
MyModelDetail 2 - Property name: 'some name' - Person: 'Bob'
Now what happens when I use my includes query is this :
MyModel.includes(my_model_details: [:property, :person]).where('property.property_name = ?', 'Property name')
SELECT "MY_MODEL".* FROM "MY_MODEL" WHERE (PROPERTY.PROPERTY_NAME = 'Prop')
If I use includes with where, join sql is not generated at all and so this query fails of course:
"PROPERTY"."PROPERTY_NAME": invalid identifier
Why is that, I'm using rails 4? How can I make it work with both joins and includes
I think it should be properties, because table name is pluralized according to CoC, try using properties.property_name instead
MyModel.includes(my_model_details: [:property, :person]).where('properties.property_name = ?', 'Property name')
As suggested by BroiSatse, add .references(:properties) to your query
Hope that helps!

calling child class method in parent class method

I have rails 4 application and this models
class Product < AR::Base
default_scope -> { where(product_type: self.to_s) }
after_initialize { self.product_type = self.class.to_s }
end
and many others like this
class Orange < Product #Apple, Carrot, ...
end
if i call in console >> Orange.new, the callback after_initialize works like expected: it sets instance attribute product_type to Orange, so self.class is determined as Orange class like i need
but
if i call >> Orange.all, the method self.to_s inside default_scope is applied to Product class =(
So, here is the question: how can i use Orange class name inside default_scope in parent class (Product) in case i do not want to write any methods inside Orange class (because there are many subclasses like Orange and i want to leave everything DRY).
And so on, if i have Apple class, it name has to be used for filtering all Product with default_scope if i call >> Apple.all (some kind of polymorphic association)
thank you.
Just use STI here:
class Product < ActiveRecord::Base
self.inheritance_column = :product_type
end
class Orange < Product
end
# then...
> Orange.all
Orange Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."product_type" IN ('Orange')
Ie. don't do the default_scope or the after_initialize, because Rails already has you covered as soon as you inherit from another model Rails will assume you're using STI and it will add the right thing in the query.

Creating model with rails

Now, I'm unable to change my db. (many millions rows in each table)
But I would like to create my rails app though.
My DB is absolutely not standard.
Table name : something_people
Fields : stid, stname, stfirstname, stage
I know to change the table_name:
class People < ActiveRecord::Base
self.table_name = "something_people"
end
I would like to rename too the fields. To use the symbol in my rails app and when I'll change the db structure, I only need to change the model classes.
class People < ActiveRecord::Base
self.table_name = "something_people"
self.field_name(:id) = "stid"
self.field_name(:name) = "stname"
self.field_name(:firstname) = "stfirstname"
self.field_name(:age) = "stage"
end
an example of query :
#countid = People.where(hash_of_conds).count(:id)
is
SELECT COUNT(stid) FROM something_people WHERE myconditions;
Asked question : How to do what I want to do ?
If you don't understand, tell me.
Thank you.
you can create aliases
class User < Activerecord::Base
alias_attribute :id, :stid
alias_attribute :name, :stname
end
But when you have a query like this
User.where("stname like '%ab%'")
you will have to specify the actual column name in the database

How to order by in Rails?

I'm working on a small blog engine.
There are the following tables: Blog and Message.
Blog has a foreign key: last_message_id, so I access the last message in the blog by calling blog.last_message
I have the following code to make it work:
class Blog < ActiveRecord::Base
belongs_to :last_message, :class_name => "Message"
end
I need to order the blogs by the last messages. But when I call
blogs.order("last_message.created_at DESC")
It doesn't work. I get the following error:
PGError: ERROR: missing FROM-clause entry for table "last_message"
ORDER BY last_messa...
How can I make it work?
UPDATE
Here's the solution:
blogs.joins(:last_message).order("messages.created_at DESC").
I think your model is wrong. See rails automaticly add 2 attributes to a model : created_at and update_at. So having a relationship like you describe is redondant. To me, it should look like this :
#model/blog.rb
class Blog < ActiveRecord::Base
has_many :messages
end
#model/message.rb
class Message < ActiveRecord::Base
belongs_to :blog
end
Then, to get the blogs ordered by the last message, you could do this :
Blog.joins(:messages).order("messages.created_at_desc")
That as you may have noticed will give you double entries for your blog model. If that is not a problem, go ahead. If it is, you have two options : doing a each and test if you already saw the blog - if not, you display it. Or, you could write your own sql.
You have to make sure the last-messages are also selected to make that order-command work.\
So something like:
blogs.includes(:last_message).order("last_message.created_at desc")

Resources