while writing some application for personal use. I find out the child query is not as great as it look.
For instance, I have 2 object
Category has_many Files
File belongs_to Category
File.category will access its parent category. But this lead to the famous N+1 problem.
For example, I want my homepage to list out 20 newest files and its corresponding category using something like this
for file in #files
%p
= file.name
= file.category.name
How should I solve this problem?
#files = File.find(:all, :limit => 20, :order => "created at desc", :include => :category)
In your find if you say :include => :category then this will eager load the categories for you and avoid a separate query to retrieve each category's name. So for your example of the 20 most recent files you could do:
#files = File.find :all, :limit => 20, :include => :category,
:order => 'created at desc'
Related
I have such code
#pre_articles = Article.find(:all, :conditions => { :ART_ID => #linkla.map(&:LA_ART_ID)})
#articles = Kaminari.paginate_array(#pre_articles).page(params[:page]).per(15)
It's selecting for me array of data, but it's huge, so i decided to add pagination. It select's 15 entries for view, but also for every page (in log) i see, how sql is selecting all array as in first #pre_articles. For more speed: how to select on every page for example 0-15, 15-30, 30-45 etc entries and send it for view? now it's selecting all data, but dislpaying as i need
Oh sorry, important!:
#linkla = LinkArt.where(:LA_ID => #la_typs.map(&:LAT_LA_ID), :LA_GA_ID => #genart.map(&:GA_ID))
#articles = Article.where(:ART_ID => #linkla.map(&:LA_ART_ID)).page(params[:page]).per(15)
So looks my query. As you see depending on #linkla results i select articles, and link la is selecting many as before... how to do that he select only for my page
Solution to the stated problem:
LinkType
has_many :links
# let us assume there is a column called name
# I wasn't sure about this model from your description
GenArt
has_many :links
# let us assume there is a column called name
Link
belongs_to :article
belongs_to :link_type
belongs_to :gen_art
# columns: id, article_id, link_type_id, gen_art_id
Article
has_many :links
Assuming you the params hash contains link_type_names and gen_art_names
Article.joins(:links => [:link_type, :gen_art]).where(
:links => {
:link_type => {:name => params[:link_type_names]},
:link_type => {:name => params[:gen_art_names]}
}
).page(7).per(50)
What about using where clause instead of conditional find?
#articles = Article.where(:ART_ID: #linkla.map(&:LA_ART_ID)).page(params[:page]).per(15)
The SQL query generated will include a LIMIT clause to avoid loading unnecessary data.
I have two models, post and comment. Posts has many comments and comments belongs to posts.
What I'm trying to do, is to have a list of posts that is ordered by creation date, unless it has comments. Then it needs to take the creation date of the latest comment.
Now I know that I can include associations like this:
Post.find(:all, :include => [:comments], :order => "comments.created_at DESC, posts.created_at DESC")
But this will order all posts with comments first and then the posts without comments. I don't want either ones ordered separately, but combined.
The solution also needs to be compatible with a paginate gem like will_paginate.
Edit
I have it now working with the following code:
posts_controller.rb
#posts = Post.order('updated_at DESC').page(params[:page]).per_page(10)
comments_controller.rb
#post = Post.find(params[:post_id])
#post.update_attributes!(:updated_at => Time.now)
I'd recommend:
Add a last_commented_at date field to your Post model. Whenever someone comments on the post update that field. It de-normalizes your db structure a bit, but your query will be faster and more strait-forward.
Postgres
Post.paginate(:include => [:comments],
:order => "COALESCE(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)
MySQL
Post.paginate(:include => [:comments],
:order => "IFNULL(comments.created_at, posts.created_at)",
:page => 1, :per_page => 10)
I have a model that has ratings in it for an post.
I want to display a list of the top 5 ratings for a given post, but I am completely lost on where to start. I figure the find method might have something useful, but I'm unsure. I've even considered looping through each record getting its size, adding it to a hash, sorting the hash, etc., but that seems too complicated.
Does anyone know how I might accomplish something like this?
Thank you
Edit: I found this to get all the posts that have the rating of agree:
Post.find(:all, :include => :post_ratings, :condtions => ['post_ratings.agree = ?', true])
The only problem is I can't figure out how to get the top five ratings from this query.
Might be worth giving a little more of an example of the code you're working with but I'll answer with a few assumptions.
If you have:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
You can find the top five post ratings with:
p = Post.find(:first) # For example
p.post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five post ratings overall you can do:
PostRating.find(:all, :limit => 5, :order => 'rating desc')
UPDATE:
Following your edit it seems you have an 'agree' and a 'disagree' column. Not sure how that works in combination so I'll stick with the 'agree' column. What you'll need to do is count the ratings with agree flagged. Something like:
count_hsh PostRating.count(:group => 'post_id',
:order => 'count(*) desc',
:conditions => { :agree => true },
:limit => 5)
This will return you a hash mapping the post id to the count of agree ratings. You can then use that post_id to locate the posts themselves. The ratings are provided by the counts so the individual ratings are (I think) of no use though you could access them by calling post.post_ratings.
So, to get the top five posts:
#top_five_posts = []
count_hsh.each_pair do |post_id, ratings|
p = Post.find(post_id)
p[:rating_count] = ratings
#top_five_posts << p
end
This is probably more verbose than it could be but is hopefully illustrative. The p[:rating_count] is a virtual attribute which isn't in the database but will allow you to access the .rating_count method on the posts in your view if you wish to.
Assuming the same Post and PostRating from Shadwell's answer:
class Post
has_many :post_ratings
end
class PostRating
belongs_to :post
# Has a 'rating' attribute
end
To get the top five ratings for all Posts:
post_ratings.find(:all, :limit => 5, :order => 'rating desc')
To get the top five ratings for a specific Post you can:
p = Post.find(:first)
post_ratings.find_all_by_post_id(p.id, :limit => 5, :order => 'rating desc')
To find all posts sorted by average rating, you can use ActiveRecord::Calculations.
PostRating.average(:rating, :group => :post_id, :include => :post, :order => 'average desc')
I have posts which are sent by users to other users. There are two models - :post and :user, and :post has the following named associations:
belongs_to :from_user, :class_name => "User", :foreign_key => "from_user_id"
belongs_to :to_user, :class_name => "User", :foreign_key => "to_user_id"
Both :user and :post have "is_public" column, indicating that either a single post and/or the entire user profile can be public or private.
My goal is to fetch a list of posts which are public AND whose recipients have public profiles. At the same time, I would like to "include" both sender and recipient info to minimize the # of db calls. The challenge is that I am effectively "including" the same table twice via the named associations, but in my "conditions" I need to make sure that I only filter by the recipient's "is_public" column.
I can't do the following because "conditions" does not accept an association name as a parameter:
Post.find(:all, :include => [ :to_user, :from_user ],
:conditions => { :is_public => true, :to_user => { :is_public => true }})
So, one way I can accomplish this is by doing an additional "join" on the "users" table:
Post.find(:all, :include => [ :to_user, :from_user ],
:joins => "inner join users toalias on posts.to_user_id = toalias.id",
:conditions => { :is_public => true, 'toalias.is_public' => true })
Is there a better, perhaps cleaner, way to do this?
Thanks in advance
I was facing the same problem and found a solution after watching sql query generated from rails query, sql query automatically generates an alias
try this,
Post.find(:all, :include => [ :to_user, :from_user ],
:conditions => { :is_public => true, 'to_users_posts.is_public' => true })
It worked for me :)
I have not been able to find a better solution than the one originally stated in my question. This one doesn't depend on how Rails names/aliases tables when compiling a query and therefore appears to be cleaner than the alternatives (short of using 3rd party gems or plugins):
Post.find(:all, :include => [ :to_user, :from_user ],
:joins => "inner join users toalias on posts.to_user_id = toalias.id",
:conditions => { :is_public => true, 'toalias.is_public' => true })
If you are on Rails 3, take a look at squeel gem, esp if you are doing these kind of complex joins often. Or if you dont want to add a extra gem, take look at the Arel table in Rails 3.
I very pleasant for that question, because I've tried to find proper solution about 2h, so after using few tips above, I've found the proper, in my case, solution. My case:
I need filter instances by created_by_id/updated_by_id fields, that fields are in every table, so...what I did
In concern 'Filterable' I wrote the method and when I needed filter by that fields I used that ->
key = "#{key.pluralize}_#{name.pluralize.downcase}.email" if %w(created_by updated_by).include?(key)
# in case with invoices key = 'updated_bies_invoices.email'
results = results.eager_load(:created_by, :updated_by).where("#{key} = ?", value)
Say I have a model Taggable has_many tags, how may I find all taggables by their associated tag's taggable_id field?
Taggable.find(:all, :joins => :tags, :conditions => {:tags => {:taggable_id => [1,2,3]}})
results in this:
SELECT `taggables`.* FROM `taggables` INNER JOIN `tags` ON tags.taggable_id = taggables.id WHERE (`tag`.`taggable_id` IN (1,2,3))
The syntax is incredible but does not fit my needs in that the resulting sql returns any taggable that has any, some or all of the tags.
How can I find taggables with related tags of field taggable_id valued 1, 2 and 3?
Thanks for any advice. :)
UPDATE:
I have found a solution which I'll post for others, should they end up here. Also though for the attention of others whose suggestions for improvement I'd happily receive. :)
Taggable.find(:all, :joins => :tags, :select => "taggables.*, tags.count tag_count", :conditions => {:tags => {:taggable_id => array_of_tag_ids}}, :group => "taggables.id having tag_count = #{array_of_tag_ids.count}"))
Your question is a bit confusing ('tags' seemed to be used quite a bit :)), but I think you want the same thing I needed here:
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }
or (if you want the results to be unique, which you probably do):
Taggable.find([1,2,3], :include => :tags).tags.map { |t| t.taggables }.flatten.uniq
This gets at all the taggables that have the same tags as the taggables that have ids 1,2, and 3. Is that what you wanted?