acts as taggable on count across multiple models - ruby-on-rails

I am trying to find the total number of tag counts across multiple models.
In one model, it works great:
Post.tag_counts_on(:hashtags).order('count desc')
However, I had another model as well called Organization that also takes hashtags. That leaves me with 2 separate counts:
Post.tag_counts_on(:hashtags).order('count desc')
Organization.tag_counts_on(:hashtags).order('count desc')
However, I want to be able to get the total counts across the two. When I try to add them, they don't merge, but instead just stack the tables on one another:
Post.tag_counts_on(:hashtags).order('count desc') +
Organization.tag_counts_on(:hashtags).order('count desc')
There should be a method for all the associated models that allows the equivalent of something like:
Hashtag.tag_counts.order('count desc')
Any ideas?

I think that you can go with a custom select
SELECT tags.name, count(*) as tag_count
FROM tags JOIN taggings on taggings.tag_id = tags.id
where taggings.taggable_type = 'Post' or taggings.taggable_type='Organization'
GROUP BY tags.name
ORDER BY tag_count DESC
use arel to build this query if needed

Related

How sorting works in ruby on rails? when there are multiple fields to sort in a single query

I have a model with the fields price, min_price,max_price, discount,in my product table. if I want to execute ascending descending orders, how that will get executed when we apply for an order on multiple fields. for example like below.
#products = Product.order("price asc").order("min_price desc").order("max_price asc").order("updated_at asc") (Query might be wrong but for reference im adding)
will it order as per the order sequence ?
If you append .to_sql to that, it will show the generated SQL so you can investigate yourself.
I tried a similar query:
Book.select(:id).order("id asc").order("pub_date desc").to_sql
=> "SELECT \"books\".\"id\" FROM \"books\" ORDER BY id asc, pub_date desc"
You might instead:
Book.select(:id).order(id: :asc, pub_date: :desc).to_sql
=> "SELECT \"books\".\"id\" FROM \"books\" ORDER BY \"books\".\"id\" ASC, \"books\".\"pub_date\" DESC"
... which you see adds the table name in, so is more reliable when if you are accessing multiple tables

Sorting 2 arrays that have been added together

In my app, users can create galleries that their work may or may not be in. Users have and belong to many Galleries, and each gallery has a 'creator' that is designated by the gallery's user_id field.
So to get the 5 latest galleries a user is in, I can do something like:
included_in = #user.galleries.order('created_at DESC').uniq.first(5)
# SELECT DISTINCT "galleries".* FROM "galleries" INNER JOIN "galleries_users" ON "galleries"."id" = "galleries_users"."gallery_id" WHERE "galleries_users"."user_id" = 10 ORDER BY created_at DESC LIMIT 5
and to get the 5 latest galleries they've created, I can do:
created = Gallery.where(user_id: id).order('created_at DESC').uniq.first(5)
# SELECT DISTINCT "galleries".* FROM "galleries" WHERE "galleries"."user_id" = 10 ORDER BY created_at DESC LIMIT 5
I want to display these two together, so that it's the 5 latest galleries that they've created OR they're in. Something like the equivalent of:
(included_in + created).order('created_at DESC').uniq.first(5)
Does anyone know how to construct an efficient query or post-query loop that does this?
order isn't available, but you can use sort - or sort_by as suggested by jvnill in this answer
As you've stated, you can use the following code:
(included_in + created).sort_by(&:created_at).uniq.first(5)
How about:
Gallery.joins("LEFT JOIN galleries_users ON galleries.id = galleries_users.gallery_id")
.where("galleries_users.user_id = :user_id OR galleries.user_id = :user_id", user_id: id)
.order("galleries.created_at DESC")
.limit(5)
I'm assuming the name of your HABTM join table is galleries_users (which is the default).
Union the two queries and select the order from the union.

Rails: How to sort many-to-many relation

I have a many-to-many relationship between a model User and Picture. These are linked by a join table called Picturization.
If I obtain a list of users of a single picture, i.e. picture.users -> how can I ensure that the result obtained is sorted by either creation of the Picturization row (i.e. the order at which a picture was associated to a user). How would this change if I wanted to obtain this in order of modification?
Thanks!
Edit
Maybe something like
picture.users.where(:order => "created_at")
but this created_at refers to the created_at in picturization
Have an additional column something like sequence in picturization table and define sort order as default scope in your Picturization
default_scope :order => 'sequence ASC'
If you want default sort order based on modified_at then use following default scope
default_scope :order => 'modified_at DESC'
You can specify the table name in the order method/clause:
picture.users.order("picturizations.created_at DESC")
Well, in my case, I need to sort many-to-many relation by a column named weight in the middle-table. After hours of trying, I figured out two solutions to sort many-to-many relation.
Solution1: In Rails Way
picture.users.where(:order => "created_at")
cannot return a ActiveRecord::Relation sorted by Picturization's created_at column.
I have tried to rewrite a default_scope method in Picturization, but it does not work:
def self.default_scope
return Picturization.all.order(weight: :desc)
end
Instead, first, you need to get the ids of sorted Picturization:
ids = Picturization.where(user_id:user.id).order(created_at: :desc).ids
Then, you can get the sorted objects by using MySQL field functin
picture.users.order("field(picturizations.id, #{ids.join(",")})")
which generates SQL looks like this:
SELECT `users`.*
FROM `pictures` INNER JOIN `picturizations`
ON `pictures`.`id` = `picturizations`.`picture_id`
WHERE `picturizations`.`user_id = 1#for instance
ORDER BY field(picturizations.id, 9,18,6,8,7)#for instance
Solution2: In raw SQL Way
you can get the answer directly by using an order by function:
SELECT `users`.*
FROM `pictures` INNER JOIN `picturizations`
ON `pictures`.`id` = `picturizations`.`picture_id`
WHERE `picturizations`.`user_id = 1
order by picturizations.created_at desc

How do I select only the associated objects in a Rails "where" query?

I have a model Category, which has_many Products, and a Product in turn has_many Categories. When a user searches for a Category, I'd like to return the products of the matching Categories without losing my Arel object. Here's what I have so far:
Category.where("upper(title) like ?", search_term.upcase).map {|category| category.products}.flatten
This does the trick of returning the products, but of course what I have is an array and not Arel. I can get as far as adding an :includes(:products) clause, so I do indeed get the products back but I still have them attached to their categories. How do I adjust my query so that all I get back is an Arel that only addresses products?
If it is products that you want then you should probably start with the Product object when you are searching. For example ,you could do it like this:
Product.joins(:categories).where("upper(categories.title) like ?", search_term.upcase)
The reason I use joins instead of includes is that joins will perform an INNER JOIN instead of LEFT OUTER JOIN which is what you need to only return the products that are actually associated with the found categories.
To make it a little more elegant you could wrap it all up in a scope in your Product model like this:
# In Product.rb
scope :in_categories_like, Proc.new{ |search_term|
joins(:categories).where("upper(categories.title) like ?", search_term.upcase)
}
# In use
#products = Product.in_categories_like(params[:search_term])

Rails ActiveRecord Join

I'm using rails and am trying to figure out how to use ActiveRecord within the method to combine the following into one query:
def children_active(segment)
parent_id = Category.select('id').where('segment' => segment)
Category.where('parent_id'=>parent_id, 'active' => true)
end
Basically, I'm trying to get sub categories of a category that is designated by a unique column called segment. Right now, I'm getting the id of the category in the first query, and then using that value for the parent_id in the second query. I've been trying to figure out how to use AR to do a join so that it can be accomplished in just one query.
You can use self join with a alias table name:
Category.joins("LEFT OUTER JOIN categories AS segment_categories on segment_categories.id = categories.parent_id").where("segment_categories.segment = ?", segment).where("categories.active = ?", true)
This may looks not so cool, but it can implement the query in one line, and there will be much less performance loss than your solution when data collection is big, because "INCLUDE IN" is much more slower than "JOIN".

Resources