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.
Related
having a play with scopes tonight in rails 3, and trying to get my head around what lambda does?
What I am trying to achieve in this example is get a list of country names (:name) from my countries model which is associated with my recipe model. A recipe belongs_to country and a country has many recipes.
I would like to order the recipes by amount of times they appear in a recipe, starting with the highest..
So i am trying this in my recipe model ( or should I do it within the country model?, but then that wouldn’t work would it as my country model is pre populated with 1 instance of every country in the world)
scope :top_countries, lambda { joins(:countries).merge(Country.name).order("name DESC") }
however i get this error message
undefined method `default_scoped?' for "Country":String
my controller
#toprankingcountry = Recipe.top_countries
obviously my understanding is not what i thought and would appreciate some pointers/assistance
Thanks
I believe the problem is with merge(Country.name) part. joins(:countries) returns ActiveRecord::Relation instance. Its merge method expects one argument which is another instance of a ActievRecord::Relation, whereas you merge string Country.name.
Generally speaking, getting a list of top countries effectively means getting a list of Countries order by some additional condition. So I'd put this logic into a Country model.
class Country
has_many :recipes
def self.top_countries
joins(:recipes).
select('countries.*, count(*) AS recipes_count').
group('countries.id').
order('recipes_count DESC')
end
end
Also if you are using RDBMS which cannot figure out dependent rows on its own (like PostgreSQL < 9.1) you'll have to manually list all the columns in group by clause.
get a list of country names (:name) from my countries model which is associated with my recipe model (instance)
I believe it is impossible to achieve what you described as is because a country has_many recipes, not vice versa
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
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'
I am using Rails v2.3.2.
I have a model called UsersCar:
class UsersCar < ActiveRecord::Base
belongs_to :car
belongs_to :user
end
This model mapped to a database table users_cars, which only contains two columns : user_id, car_id.
I would like to use Rails way to count the number of car_id where user_id=3. I konw in plain SQL query I can achieve this by:
SELECT COUNT(*) FROM users_cars WHERE user_id=3;
Now, I would like to get it by Rails way, I know I can do:
UsersCar.count()
but how can I put the ...where user_id=3 clause in Rails way?
According to the Ruby on Rails Guides, you can pass conditions to the count() method. For example:
UsersCar.count(:conditions => ["user_id = ?", 3])
will generates:
SELECT count(*) AS count_all FROM users_cars WHERE (user_id = 3)
If you have the User object, you could do
user.cars.size
or
user.cars.count
Another way would be to do:
UserCar.find(:user_id => 3).size
And the last way that I can think of is the one mentioned above, i.e. 'UserCar.count(conditions)'.
With the belogngs to association, you get several "magic" methods on the parent item to reference its children.
In your case:
users_car = UsersCar.find(1) #=>one record of users_car with id = 1.
users_car.users #=>a list of associated users.
users_car.users.count #=>the amount of associated users.
However, I think you are understanding the associations wrong, based on the fact that your UsersCar is named awkwardly.
It seems you want
User has_and_belongs_to_many :cars
Car has_and_belongs_to_manu :users
Please read abovementioned guide on associations if you want to know more about many-to-many associations in Rails.
I managed to find the way to count with condition:
UsersCar.count(:condition=>"user_id=3")
models:
#StatusMessage model
class StatusMessage < ActiveRecord::Base
belongs_to :users
default_scope :order => "created_at DESC"
end
#User Model
class User < ActiveRecord::Base
has_many :status_messages
end
In controller I want to join these two tables and get fields from both table. for example I want email field from User and status field from StatusMessage. When I use :
#status = User.joins(:status_messages)
Or
#status = User.includes(:status_messages)
It gives me only the user table data.
How can I implement this requirement?
You need to use includes here. It preloads data so you won't have another SQL query when you do #user.status_messages.
And yes you can't really see the effect: you need to check your logs.
First of all, I don't think it is possible (and reasonable) what you want to do. The reason for that is that the relation between User and StatusMessage is 1:n, that means that each user could have 0 to n status messages. How should these multitudes of attributes be included in your user model?
I think that the method joints in class ActiceRecord has a different meaning, it is part of the query interface. See the question LEFT OUTER joins in Rails 3
There are similar questions on the net, here is what I have found that matches most:
Ruby on Rails: How to join two tables: Includes (translated for your example) in the user a primary_status_message, which is then materialized in the query for the user. But it is held in one attribute, and to access the attributes of the status_message, you have to do something like that: #user.primary_status_message.status
When you use #status = User.includes(:status_messages) then rails eagerley loads the data of all the tables.
My point is when you use this User.includes(:status_messages) it will loads the data of status_messages also but shows only users table data then if you want first user status_messages then you have to #user.first.status_messages