How to order by in Rails? - ruby-on-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")

Related

Rails 4: Active Record get entries based on belongs_to relation's properties

I've not been able to figure this one out -- perhaps I've missed something in the docs. I want to get all of the articles that are associated with published issues. (That is, article.issue.is_published = true.)
I have two models:
# Article model
class Article < ActiveRecord::Base
belongs_to :issue
...
end
# Issue model
class Issue < ActiveRecord::Base
has_many :articles
...
end
From what I understand, I can run something like this:
Article.includes(:issues)
.where('issue.is_published = true')
.references(:issues)
But I get nothing but funky business on the other end. All of the examples I've found have one-to-many relationships going the other way - I assumed that I've gotten something wrong with the naming convention, but no combination of "issue" and "issues" seems to work any better.
What am I missing here?
You'll want to do a joins instead of includes here. This translates to a SQL inner join. Article.joins(:issue).where(issues: {is_published: true})

undefined method 'article' - use data from other model

There are articles and comments.
article has_many :comments
comment belongs_to :article
I want to get all comments where value_id is equal to value_id attribute in the article that comment belongs_to.
class Comment < ActiveRecord::Base
belongs_to :article
def self.value_comments
where(value_id: self.article.value_id)
end
end
I get an error:
undefined method `article' for #<Class:0x007fd2a7e46d18>
controller
#value_comments = Comment.value_comments.where(user_id: current_user.id).order("created_at desc")
Having read your question again, my understanding is that you want to find all Comments whose value_id matches the value_id of their associated Article.
Your code is nearly correct - you need a few more parts to get this to work. You need to join your Comment table to your Article table using joins. Then, refer to the column in a where function using arel_table.
So you should end up with something like this:
def self.value_comments
joins(:article).where(self.arel_table[:value_id].eq Article.arel_table[:value_id])
end
You could also consider using sexy_scopes to make it easier to access your columns.

Understanding Rails associations

I'm learning Rails and I'm trying to connect the dots between Ruby and what's going on when creating associations. For example:
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
end
I've read an explanation online that relates the use of belongs_to and has_many here to attr_accessor in Ruby. This is a tad confusing ... how is that so? I understand this sets up the 1:M association between Post and User, specifically Post has a foreign key containing a user id. In the rails console, I can do something like:
user = User.first
user.posts
user2 = User.create(username: 'some guy').save
post2 = Post.new(title: 'stuff', body: 'some more stuff')
user2.posts << post2
So are these kind of like 'getter' and 'setter' methods where an object of each class corresponds to a specific row in the database and I can use these methods because of their association/relationship?
To answer your exact question, the answer is "kinda yes, kinda no".
What rails does internally to set up the association is pretty complicated. yes, getter/setter methods are involved, yes, pointing at table rows are involved... but this isn't exactly what Active Record does, and it isn't only what Active Record does.
If you really want to know what Active Record does inside: you can just go and look at the source code on github.
If you have a more specific question... I recommend you update your question to ask that :)

Get last comment of all posts in one category

Comment belongs to Post.
Post belongs to Category.
How would I get a collection of every lastly updated comment for each post, all belonging to one single category?
I've tried this but it just gives me one post:
category.posts.joins(:comments).order('updated_at DESC').first
Update
What I want is to fetch one commment per post, the last updated comment for each post.
Rails doesn't do this particularly well, especially with Postgres which forbids the obvious solution (as given by #Jon and #Deefour).
Here's the solution I've used, translated to your example domain:
class Comment < ActiveRecord::Base
scope :most_recent, -> { joins(
"INNER JOIN (
SELECT DISTINCT ON (post_id) post_id,id FROM comments ORDER BY post_id,updated_at DESC,id
) most_recent ON (most_recent.id=comments.id)"
)}
...
(DISTINCT ON is a Postgres extension to the SQL standard so it won't work on other databases.)
Brief explanation: the DISTINCT ON gets rid of all the rows except the first one for each post_id. It decides which row the first one is by using the ORDER BY, which has to start with post_id and then orders by updated at DESC to get the most recent, and then id as a tie-breaker (usually not necessary).
Then you would use it like this:
Comment.most_recent.joins(:post).where("posts.category_id" => category.id)
The query it generates is something like:
SELECT *
FROM comments
INNER JOIN posts ON (posts.id=comments.post_id)
INNER JOIN (
SELECT DISTINCT ON (post_id) post_id,id FROM comments ORDER BY post_id,updated_at DESC,id
) most_recent ON (most_recent.id=comments.id)
WHERE
posts.category_id=#{category.id}
Single query, pretty efficient. I'd be ecstatic if someone could give me a less complex solution though!
If you want a collection of every last updated Comment, you need to base your query on Comment, not Category.
Comment.joins(:post).
where("posts.category_id = ?", category.id).
group("posts.id").
order("comments.updated_at desc")
What you're basically asking for is a has_many :through association.
Try setting up your Category model something like this:
class Category < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
Then you can simply do this to get the last 10 updated comments:
category.comments.order('updated_at DESC').limit(10)
You could make this more readable with a named scope on your Comment model:
class Comment < ActiveRecord::Base
scope :recently_updated, -> { order('updated_at DESC').limit(10) }
end
Giving you this query to use to get the same 10 comments:
category.comments.recently_updated
EDIT
So, a similar solution for what you actually wanted to ask for, however it requires you to approach your associations from the Comment end of things.
First of all, set up an association on Comment so that it has knowledge of its Category:
class Comment < ActiveRecord::Base
belongs_to :post
has_one :category, through: :post
end
Now you can query your comments like so:
Comment.order('updated_at desc').joins(:post).where('posts.category' => category).group(:post_id)
Somewhat long-winded, but it works.
.first is grabbing only one for you. The first one to be exact. So drop the .first. So instead do:
category.posts.joins(:comments).order('updated_at DESC')

How to I make my custom mySQL query to in rails 3?

Im trying to display recently added comments from tattoos a user has posted. So If I posted a tattoo, and then user_b posted "hey I like your tattoo" then Im trying to get just the comment.
First of all Im using the acts_as_commentable_with_threading gem which doesnt create a foreign key for the table im trying to join. So my controller cant look for tattoo_id, it has to look for commentable_id
In the controller I would have to call the Comment model and then pass some SQL stuff into it but apparently I have no clue how to pass custom SQL queries into ruby because even tho my query string works in terminal, I get all sorts of nonsense when trying to use it in rails.
Im basically trying to do this:
SELECT comments.id FROM comments,tattoos WHERE commentable_id = tattoos.id AND
tattoos.member_id = #{current_user}
where #{current_user} will be the current_user passed in.
You don't have to jump through so many hoops to accomplish this. acts_as_commentable assigns a polymorphic association, so you should set it up like this:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
class Tattoo < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class User
has_many comments
end
Then you can access the association as usual:
Tattoo.where(:member_id => current_user).first.comments
See http://railscasts.com/episodes/154-polymorphic-association for a general tutorial on how polymorphic associations work. It just so happens that this railscast uses exactly :commentable as the polymorphic example, so you should be able to follow along directly if you want.
I think Ben's approach is best but for future reference if you do come across something more complicated you can always use sql for example:
Comment.find_by_sql("SELECT comments.* FROM comments,tattoos WHERE commentable_id = tattoos.id AND tattoos.member_id = ?", current_user)

Resources