class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
I'm trying to pull out Post data, while eager loading the User and Comment data as well, but with the limitation of not loading the Comments which have been blocked (TINYINT field in the Comment table). The following works when there are comments present, but it's causing issues when I load posts that don't have any comments yet:
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => "comments.blocked = 0")
Any suggestions on how I can run this query such that it will work when no comments are present? Thanks.
What error does it give when you try to do that on a post that has no comments?
Update:
What about this variation?
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => {:comments => {:blocked => false}})
Conditions on eagerly loaded associations is sort of unusual. Perhaps you should be using the :joins option instead? Or skip eagerly loading the post (since it's just a single one), and have a named scope for the non-blocked comments to use in your view. Something like this perhaps:
#post = Post.find(params[:id])
#comments = #post.comments.where(:blocked => false).all(:include => :user)
(Just typing off the cuff here, not certain that's exactly the right syntax for you)
OK, so after a bit of fiddling, here's the answer (which may seem obvious to some, but the count() function in MySQL was giving me some grief):
#post = Post.find(params[:id],
:include => {:comments => :user},
:conditions => "comments.blocked = 0 OR posts.comments_count = 0")
comments_count is a counter_cache field, so I'm getting around the explicit use of the MySQL count() function. It feels like a kludge, but for now I'm OK with that. If anyone has a more elegant solution, please let me know!
Related
I have a very complex query at a beginner level which I am not able to solve out. It takes my page load to around 6 7 seconds on local machine.
The Query is something like for user
self.groups.collect(&:reports).flatten.paginate
Now I run a loop which is something like :
reports.each do |report|
report.name
report.user.name
report.user.image.attachment.url(:thumb) #User has_one :image , Image belongs_to :user
report.questions.each do |question|
question.name
question.answers.each do |answer|
answer.name
end
end
report.comments.each do |comment|
comment.name
comment.user.name
comment.user.image.attachment.url(:thumb) #User has_one :image , Image belongs_to :user
end
end
It has a lot of nested looping which requires a lot of use of includes but me not able to figure out which way should i proceed. My earlier question was N+1 in rails relations with active records?
------------------EDIT------------------
I am able to do
self.groups.includes(:reports => [:user, :questions])
or
self.groups.includes(:reports => [:user => [:image]])
but not
self.groups.includes(:reports => [:user => [:image], :questions])
whats wrong with this ??
------------------EDIT------------------
self.groups.includes(:reports => [:questions, :user => [:image]]).collect(&:reports).flatten.paginate(:page => page, :per_page => 6)
This is what seems to work for me with no extra/less eager loading according to bullet gem( https://github.com/flyerhzm/bullet ) but still it takes some 10 seconds to load up the queries and its like a huge huge load. Any further improvements suggested ??
------------------EDIT------------------
self.groups.includes(:reports => [:questions, .........]).collect(&:reports).paginate
changed to
self.group_reports.includes(:questions, ....... ).paginate
helped a lot
In User Model defined something like
has_many :reports
has_many :group_reports, :through => :groups, :source => :reports
Try to use hash:
self.groups.includes(:reports => [:questions, {:user => :image}])
I have three models that i'd like to perform a simple search across:
class Release < ActiveRecord::Base
has_many :artist_releases
has_many :artists, :through => :artist_releases
has_many :products, :dependent => :destroy
end
class Product < ActiveRecord::Base
belongs_to :release
has_many :artists, :through => :releases
end
class Artist < ActiveRecord::Base
has_many :artist_releases
has_many :releases, :through => :artist_releases
end
In my Product Controller i can successfully render a product list searching across release and product using:
#products = Product.find(:all, :joins => :release, :conditions => ['products.cat_no LIKE ? OR releases.title LIKE ?', "%#{params[:search]}%","%#{params[:search]}%"])
I really need to be able to search artist as well. How would I go about doing that? I ideally need it within the Product Controller as it's a product list I need to display.
I've tried adding :joins => :artist and variations thereof, but none seem to work.
I'm aware there are options out there like Sphinx for a full search, but for now I just need this simple approach to work.
Thanks in advance!
if you only want products back, just add both joins:
#products = Product.joins(:release,:artists).where('products.cat_no LIKE :term OR releases.title LIKE :term OR artists.name LIKE :term', :term => "%#{params[:search]}%").all
You may also need group_by to get distinct products back.
if you want polymorphic results, try 3 separate queries.
I know I'm suggesting a simple approach (and probably not the most efficient) but it will get your job done:
I would create a method in your Product model similar to this:
def find_products_and_artists
results = []
Product.find(:all, :conditions => ['products.cat_no LIKE ?', "%#{params[:search]}%"]).each do |prod|
results << prod
end
Release.find(:all, :conditions => ['releases.title LIKE ?', "%#{params[:search]}%"]).each do |rel|
results << rel
end
Artist.find(:all, :conditions => ['artist.name LIKE ?', "%#{params[:search]}%"]).each do |art|
results << art
end
return results
end
Then when you call it the method and store the returned results in a variable (e.g. results), you can check what object each element is by doing
results[i].class
and can make your code behave accordingly for each object.
Hope I helped.
If I have something like this:
class Post < ActiveRecord::Base
has_many :comments, :as => :commentable do
def approved
find(:all, :conditions => {:approved => true})
end
end
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
...when I do this, I get 2 hits to the database (not including finding the post :p):
post = Post.first
post.comments #=> [Comment1, Comment2...]
post.comments.approved #=> [Comment1, Comment7...]
It seems like it should just filter the current comments array in memory, no? Is it doing that? Reason I ask is because the console shows SELECT * FROM ... on post.comments.approved, even though I already called post.comments. Shouldn't this be better optimized in ActiveRecord?
The AR executes a new query for any finder calls inside a association extension method.
You can refer to the cached result set by using self.
has_many :comments, :as => :commentable do
def approved
# uses the cached result set
self.select{|c| c.approved == true}
end
end
It's optional, as in some cases you might only want to load the associated objects when needed. If you want them all loaded into memory, you need to explicitly declare the objects you'd like included with the initial query, using the :include flag. Example:
post = Post.find(:first, :include => :comment)
You might have to rewrite your extension to take advantage of the feature... an approach would be to change your "approved" function to iterate through the comments array attached to each post, and return a new array with the nonapproved comments filtered out. The "find" you have defined explicitly goes back to the database.
If your query is really that simple, then what you want is a named scope:
class Comment
named_scope :approved, :conditions => {:approved => true}
end
Then, you can do:
#post.comments.approved.count #=> 1 DB hit!
#post.comments.count #=> Another DB hit - can't reuse same scope
Look at #scope (#named_scope in Rails 2.3).
Need a little help with a SQL / ActiveRecord query. Let's say I have this:
Article < ActiveRecord::Base
has_many :comments
end
Comment < ActiveRecord::Base
belongs_to :article
end
Now I want to display a list of "Recently Discussed" articles - meaning I want to pull all articles and include the last comment that was added to each of them. Then I want to sort this list of articles by the created_at attribute of the comment.
I have watched the Railscast on include /joins - very good, but still a little stumped.
I think I want to use a named_scope, something to this effect:
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, :include => :comments, :conditions => { some_way_to_limit_just_last_comment_added }, :order => "comments.created_at DESC"
end
Using MySQL, Rails 2.3.4, Ruby 1.8.7
Any suggestions? :)
You have two solutions for this.
1) You treat n recent as n last. Then you don't need anything fancy:
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, :include => :comments,
:order => "comments.created_at DESC",
:limit => 100
end
Article.recently_commented # will return last 100 comments
2) You treat recent as in last x duration.
For the sake of clarity let's define recent as anything added in last 2 hours.
Article < ActiveRecord::Base
has_many :comments
named_scope :recently_commented, lambda { {
:include => :comments,
:conditions => ["comments.created_at >= ?", 2.hours.ago]
:order => "comments.created_at DESC",
:limit => 100 }}
end
Article.recently_commented # will return last 100 comments in 2 hours
Note Code above will eager load the comments associated with each selected article.
Use :joins instead of :include if you don't need eager loading.
You're gonna have to do some extra SQL for this:
named_scope :recently_commented, lambda {{
:select => "articles.*, IFNULL(MAX(comments.created_at), articles.created_at) AS last_comment_datetime",
:joins => "LEFT JOIN comments ON comments.article_id = articles.id",
:group => "articles.id",
:conditions => ["last_comment_datetime > ?", 24.hours.ago],
:order => "last_comment_datetime DESC" }}
You need to use :joins instead of :include otherwise Rails will ignore your :select option. Also don't forget to use the :group option to avoid duplicate records. Your results will have the #last_comment_datetime accessor that will return the datetime of the last comment. If the Article had no comments, it will return the Article's created_at.
Edit: Named scope now uses lambda
I have the following models:
class Person < ActiveRecord::Base
has_many :images
has_one :preference
end
class Image < ActiveRecord::Base
belongs_to :person
end
class Preference < ActiveRecord::Base
belongs_to :person
end
I am trying to fetch all images that are public and at the same time eager load the people who own those images:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference], :include => :person)
It appears Rails does not like the :include (I believe because :person is referenced in 2 models). This is the error I get (which disappears when I drop the :include option):
"ActiveRecord::StatementInvalid: Mysql::Error: Not unique table/alias: 'people'"
I can get around this by writing out the actual JOIN command as a string and passing it into the :include option, but this not Rails-y so I was hoping there's a cleaner way to do this.
Any help would be much appreciated.
Thanks!
It looks like you call it "preferences", and not user_preferences. So you join should be:
:joins => [:person => :preference])
By using JOIN you are actively including the People table, so you shoudn't need to add "include" again. This should work:
Image.find(:all, :conditions => ["images.person_id = ? AND preferences.image_privacy = ?", user.id, PRIVACY_PUBLIC],
:joins => [:person => :user_preference])
It might be the issue with Table Aliasing, rails doc has great details in Table Aliasing
also, post SQL here will be useful too.
You wrote :conditions => ["images.person_id", user.id] and you said that you want to load images and people who owns those images. But it looks like you are loading images that belongs to one person (not to group of people), because you specify only one user.id.
I would do it this way:
Person.find(user.id, :include => [:images, :preference], :conditions => ["preferences.image_privacy = ?", PRIVACY_PUBLIC])
It will load person and his/her images.
Probably I don't understand your problem correctly, because what I think you want to do doesn't seem logic to me.
Instead of using conditions you can try named_scope