Relation/Sort not working in rails controller? - ruby-on-rails

I have the following relation in my rails app:
genre
- has many - authors
authors - belong to genre and has many books
books - belongs to authors and belongs to users
(users can add books to the db)
in my controller I have:
#books=current_user.books(:include => [:author => :genre], :order => 'created_at DESC')
While I am able to use the #books variable in my views - nothing is done correctly (i.e. its not showing me only books added by that user, and its not descending by created_at)...
any ideas?
--
Also I'm using clearance for the user auth, so current_user without the # in the controller seems to work fine
Actually, I think the relation is working, only the sort might not be working...

Which created_at do you want to sort on? When you're engaging a join, it's usually safer to be specific:
#books = current_user.books(..., :order => 'books.created_at DESC')
The other thing you can do is sort it in the client which may be faster than having the RDBMS do it if you're not paginating:
#books = current_user.books(...).sort_by(&:created_at).reverse
If you're paginating these results you will need to sort in the query, there's no way around it.

Related

Devise and acts_as_votable: where votes_for voter_id => current_user.id

I'm using the devise and acts_as_votable gems to allow users to cast their votes on posts. My current objective is to query a list of the posts liked by the user.
Looking around in the Rails console I found out that when a post is voted on a votes_for table gets appended on containing information like the timestamp, vote weight and the voter_id which is what I would like to call upon.
Here's what the user controller looks like (user controller because I'm attempting to query the likes on the users/show page):
#user = current_user
#links = Link.all
#likes = #links.where.votes_for(:voter_id => #user.id) // line I'm trying to figure out, I reckon it'd look something like this
I could definitely be going about this the wrong way altogether, if that's the case I'd just appreciate some direction.
Explanation would be appreciated... I'm learning the fundamentals of Rails and finding its naming conventions convoluted and hard to follow.
If you're using Devise, I guess the current_user method should be available in your view and you shouldn't need a reassignment to #user
To understand how ActiveRecord works you might want to look into the documentation here.
As for the links and the voter_id, here's the way I think your query should be:
#likes = Link.where(votes_fors: {voter_id: current_user.id}) # OR Link.where(voter: current_user)
Basically the query will be transformed to a SQL query which says:
"SELECT * FROM links INNER JOIN votes_fors ON links.id = votes_fors.votable_type='Link' WHERE votes_fors.voter_id = ?" [voter_id, 1]
You get the idea?
A better approach would be to use the ActiveRecord methods to fetch these collections e.g
class User
has_many :votes_fors, foreign_key: :voter_id
has_many :favorites, through: :votes_fors, source: :votable # not really sure of this syntax but it should work I think.
end
With the above, you should be able to do:
current_user.favorites which would fetch you all the links voted by the user.

Ordering by a column in an associated model

I am trying to make an ActiveRecord query that will order the results by the value of one of the columns in an associated model:
I have a Chats model that has a one to many relationship with the messages model.
Chats has_many messages and Message belongs_to chat
And from my controller, I want to get a list of chats, ordered by the created_at of the associated message.first, eg:
#chats = current_user.chats.includes(:messages).order("messages.first.created_at").paginate(page: params[:page])
or something like that.
My question is that how can I achieve this kind of ordering from an associated model with this relationship? All contributions are appreciated.
Also add .references(:messages)
This will pull messages in as a join rather than a separate query.
You can define your order in your association. Try the following:
has_many messages, -> { order(:created_at => :asc) }
# change how you need
So when you call chat.messages it will return messages with the given order.
Thank you all, #Rubyrider and #Andrew. I have been able to order the columns as follow:
#chats = current_user.chats.includes(:messages).order("messages.created_at desc").paginate(page: params[:page])
without the inclusion of either .first or .last on messages. Surprisingly, I did not have to specify which of the messages of the chat is to be used for the ordering. I guess ActiveRecord automatically looks and just takes the latest of the associated model to use for the ordering.

Ruby on Rails - How to Query on model/condition on controller?

I'm using rails 3.2.3 and have a questions about queries.
I've read that it is favorable using arel instead of named scopes.
Basically in my app, when a user logs in, I want him to see the products that he created.
So instead of having in my controllers index:
products=Product.find(:all)
I was looking for something like
products=Product.find(:all, :conditions....)
The thing is, my User and Product models have a HABTM relation (it really has to be) and I don't know how to join those tables so that only the products inserted by the current_user are displayed (the insertion is working correctly)
Do I have to add a search method in my Product model?
Or this can be accomplished by passing :conditions in the index controller?
Basically the logic is:
->Get all the products
->inner joining with the products_users HABTM table and get all the products where products_users.user_id = current_user.id. Return the results.
I don't know if I'm missing something here...any tips on how to code this? I'm kind of confused.
if user_sighed_in?
#products = current_user.products
else
#products = Product.scoped
end
ofc u have to define association in User model
has_many :products
If you have associated User and Products models - this code #products = current_user.products will return products of current_user.
To find all the products of current user this will do the trick
current_user.products

Override just the default scope (specifically order) and nothing else in Rails

So basically I have two classes, Book and Author. Books can have multiple authors and authors can have multiple books. Books have the following default scope.
default_scope :order => "publish_at DESC"
On the Author show page I want to list all the books associated with that author so I say the following...
#author = Author.find(params[:id])
#books = #author.books
All is good so far. The author#show page lists all books belonging to that author ordered by publication date.
I'm also working on a gem that is able to sort by the popularity of a book.
#books = #author.books.sort_by_popularity
The problem is that whenever it tries to sort, the default_scope always gets in the way. And if I try to unscope it before it will get rid of the author relation and return every book in the database. For example
#books = #author.books.unscoped.sort_by_popularity # returns all books in database
I'm wondering if I can use the ActiveRelation except() method
to do something like this (which seems like it should work but it doesn't. It ignores order, just not when it is a default_scope order)
def sort_by_popularity
self.except(:order).do_some_joining_magic.order('popularity ASC')
# |------------| |---------------------|
end
Any ideas as to why this doesn't work? Any ideas on how to get this to work a different way? I know I can just get rid of the default_scope but I'm wondering if there another way to do this.
You should be able to use reorder to completely replace the existing ORDER BY:
reorder(*args)
Replaces any existing order defined on the relation with the specified order.
So something like this:
def self.sort_by_popularity
scoped.do_some_joining_magic.reorder('popularity ASC')
end
And I think you want to use a class method for that and scoped instead of self but I don't know the whole context so maybe I'm wrong.
I don't know why except doesn't work. The default_scope seems to get applied at the end (sort of) rather than the beginning but I haven't looked into it that much.
You can do it without losing default_scope or other ordering
#books.order_values.prepend 'popularity ASC'

Ruby on Rails: find doesn't retrieve all objects associated when using conditions

I have 2 models with a many-to-many relationship (simplified here with books-authors example):
class Book < ActiveRecord::Base
has_and_belongs_to_many :authors
end
class Author < ActiveRecord::Base
has_and_belongs_to_many :books
end
I need to display a list of books, and under each one a list of its authors.
Users are able to filter via a certain author.
I'm trying to filter via an author in the following way:
Let's assume there's only one resulting book, and it has 3 authors.
books = Book.find(:all, :include => :authors, :conditions => "authors.id = 5")
When I try to list the different authors of the books retrieved, I get only the author with ID=5.
books.first.authors.size # => 1
books.first.authors.first.id # => 5
I would expect rails to go and retrieve all the different authors associated with the books, either during the find query or as a follow-up query, yet it does not.
How can I solve this problem?
Is there something wrong with the associations? the find query?
Thanks.
This happens because you use :include in your find method. When you do that, you tell Rails to cache the associated result (eager loading) to avoid multiple database queries. And since the conditions you have specified only includes one of the three authors, that is the only one that will be cached and then looped through when you call .authors
I think the easiest solution would be to change :include to :joins. It will perform the same database query, but it will not cache the associated authors. So when you call authors for each book, it will perform a new database call to retrieve all authors for that book. As I said, the easiest but perhaps not the best since it could potentially result in a lot of queries to the database.
Here is another way you could do it:
book_ids = Book.all(:joins => :authors,
:conditions => ["authors.id = ?", 5]).map(&:id)
books = Book.all(:include => :authors,
:conditions => ["books.id IN (?)", book_ids])
This will first collect all id's of the books related to a specific author. Then it will query the database again with the resulting book_ids and this time include all authors related to those books and cache it to avoid unnecessary db calls.
Edit:
If you really want to use only one call, then I think the only options is to write a little more SQL in the call. Perhaps like this:
books = Book.all(:include => :authors,
:conditions => ["books.id IN (SELECT book_id FROM authors_books WHERE author_id = ?)", 5])

Resources