Rails sql complex query for public_activity - sql

I'm using the public_activity gem with rails 4 to generate an activity feed. The models are as follows:
Class Post
has_many :comments
has_many :taggings
has_many :tags, through: :taggings
Class Comment
belongs_to :post
Class Tagging
belongs_to :post
belongs_to :tag
Class Tag
has_many :posts, through: :taggings
has_many :taggings
On the Tags#show action, I'd like to show a feed of all activities of posts and comments belonging to posts belonging to that particular tag. How do I write the query?
class TagsController
activities = PublicActivity::Activity.order("created_at desc").where('trackable_type =? OR trackable_type = ?', 'Post', 'Comment')
#activities =.....

SOLUTION.
Ok managed to get it working.
TagsController
def show
post_ids = #tag.posts.map(&:id)
comment_ids = Comment.where('post_id IN (?)', post_ids).map(&:id)
#activities = PublicActivity::Activity.
where("(trackable_id IN (?) AND trackable_type = 'Post')
or (trackable_id IN (?) AND trackable_type = 'Comment')", post_ids, comment_ids).
order("created_at desc")
end
It's ugly and unsatisfying but it gets the job done. Bonus points for whoever can optimize this query!

Related

ActiveRecord querying through multiple joins

So below are my models:
post.rb
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
has_many :collectables
has_many :collections, through: :collectables
end
tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag, counter_cache: :posts_count
end
tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
end
collectable.rb
class Collectable < ActiveRecord::Base
belongs_to :post
belongs_to :collection, counter_cache: :posts_count
end
collection.rb
class Collection < ActiveRecord::Base
has_many :collectables
has_many :posts
end
A Collection has many Posts, and a Post has many Tags. Now I'm trying to create a search bar that'll search for Collections by Posts that have particular Tags (tag.name is the search term). For instance, Collection 1 has a post that has the tag #cat. Now if the user searches for "cat", Collection 1 will show up in the results. I'm not sure how the query should look like for making this happen.
def self.search(search)
Collection.joins(:posts) ... ?
end
try this:
def self.search(search)
Collection.includes(posts: :tags).where(tags: {name: search})
end
Try This
def self.search(search)
Collection.joins(posts: :tags).where({tags: {name: search}})
end
You can use includes, eager_load and joins, but when you use joins then only it will take those collections who have the posts in all other cases, it will take all collections who have the posts and who don't have the posts.

What's the rails way to include a field in a join model when listing an association?

So if I have the following relationship
class Item < ActiveRecord::Base
has_many :item_user_relationships
has_many :users, :through => :item_user_relationships
end
class User < ActiveRecord::Base
has_many :item_user_relationships
has_many :items, :through => :item_user_relationships
end
class ItemUserRelationship < ActiveRecord::Base
belongs_to :item
belongs_to :user
attr_accessible :role
end
What's the rails way to include the role attribute when listing all the Users of an Item?
#users = #item.users # I want to include the role as part of a user
Thanks!
UPDATE: I'm still having trouble with this. My goal is to get an array of User models that have their role included in the attributes.
I'm note sure if I understand you correctly, but maybe this is what you want?
#users = #item.users
#users.each do |user|
puts user.item_user_relationships.first.role
end

How to sort by created_at column of association in rails?

Here are my associations:
Class Post
belongs_to :user
has_many :favorites, :dependent => :destroy
has_many :favoriters, :through => :favorites, :source => :user
end
Class User
has_many :posts
has_many :favorites, :dependent => :destroy
has_many :favorited, :through => :favorites, :source => :post
end
Class Favorites
belongs_to :user, :post
end
I want to sort users' favorite posts by the created_at column of the Favorites association. However, this sorts by the Post created_at attribute, not the Favorites created_at attribute. How can I sort by the Favorites created_at attribute?
#posts=#user.favorited.order('created_at DESC')
You need to specify which table you want to use in the order by clause.
#posts = #user.favorited.order('posts.created_at DESC')
ought to do it.
One nice trick is to use the rails console when inspecting associations. Specifically, it helps to use the 'to_sql' method on Active Record queries you are performing.
For instance:
% bundle exec rails console
> u = User.last
> u.favorited.order('created_at DESC').to_sql
use this in your post model for set default order:
default_scope { order("created_at DESC") }

Rails includes nested relations

I need to query all posts from a specific user and include all comments and the user who belongs to the comment.
class User < ...
has_many :posts
has_many :comments
end
class Post < ...
belongs_to :user
has_many :comments
end
class Comment < ...
belongs_to :user
belongs_to :post
end
#posts = current_user.posts.include(:comments)
Is is possible to also get the comment user? I list a lot of posts and comments. I do not want to query each comment user.
Thx / Tobias
Try
#posts = current_user.posts.includes( :comments => :user)
Read more about it here
How about include at the relation definition statement?
:include
Specify second-order associations that should be eager loaded when this object is loaded.
class Post <
belongs_to :user
has_many :comments, :include => [:user], :limit => 5
end

Avoiding individual database calls for count

My models look like this:
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
end
class Actor < ActiveRecord::Base
attr_accessible :name
has_many :movies, :through => :jobs
has_many :jobs, :dependent => :destroy
end
class Job < ActiveRecord::Base
attr_accessible :movie_id, :actor_id
belongs_to :movie
belongs_to :actor
end
When I'm displaying my index of Actors, I'd like to show the number of movies each actor has starred in. I can do this with #actor.movies.count, however this generates an SQL query for each actor. With, say, 30 actors, this will result in 30 extra queries in addition to the initial.
Is there any way to include the count of movies each actor has participated in, in the initial Actor.all call? And thereby getting things done with only one call. Extra bonus if this was sorted by said count.
Update:
All answers provided has been helpful, and though it turned into some dirt-slinging-contest at some point, it worked out well. I did a mish-mash of all your suggestions. I added a movies_counter column to my Actor model. In my Job model I added belongs_to :actor, :counter_cache => :movies_counter. This works brilliantly, and is automatically updated when i create or destroy a movie, without me adding any further code.
As #Sam noticed, you should add new column to actors table movies_counter
rails g migration add_movies_counter_to_actor movies_counter:integer
Now you can edit your migration
class AddMoviesCounterToActor < ActiveRecord::Migration
def self.up
add_column :actors, :movies_counter, :integer, :default => 0
Actor.reset_column_information
Actor.all.each do |a|
a.update_attribute :movies_counter, a.movies.count
end
end
def self.down
remove_column :actors, :movies_counter
end
end
And run it
rake db:migrate
Then you should add two callbacks: after_save and after_destroy
class Movie < ActiveRecord::Base
attr_accessible :title, :year, :rotten_id, :audience_score,
:critics_score, :runtime, :synopsis, :link, :image
has_many :jobs, :dependent => :destroy
has_many :actors, :through => :jobs
after_save :update_movie_counter
after_destroy :update_movie_counter
private
def update_movie_counter
self.actors.each do |actor|
actor.update_attribute(:movie_count, actor.movies.count)
end
end
end
Then you can call some_actor.movies_counter
Add a column to your Actor table called 'movie_count'. Then add a call back in your Actor model that updates that column.
class Movie < ActiveRecord::Base
has_many :actors, :through => :jobs
before_save :update_movie_count
def update_movie_count
self.actor.update_attribute(:movie_count, self.movies.size)
end
end
That way your just have an integer that gets updated instead of calling all records.