Rails: How to sort many-to-many relation - ruby-on-rails

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

Related

How to pluck id of has_one associations?

class Post
has_one :latest_comment, -> { order(created_at: :desc) }, class_name: 'Comment'
end
I want to do something like:
Post.joins(:latest_comment).pluck('latest_comment.id')
but it's not valid syntax and it doesn't work.
Post.joins(:latest_comment).pluck('comments.id')
Above works but it returns ids of all comments for a post, not only of the latest.
ActiveRecord::Assocations are a very leaky abstraction around SQL joins so your has_one :latest_comment assocation won't actually return a single row in the join table per record unless you're calling it on an instance of Post.
Instead when you run Post.joins(:latest_comment).pluck('comments.id')you get:
SELECT "comments"."id"
FROM "posts"
INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
ActiveRecord isn't actually smart enough to know that you want to get unique values from the comments table - and it actually just behaves like a has_many association. In its defence this isn't actually something thats even realistic to do in polyglot fashion.
What you want to do can instead is to select the rows from the comments table and get distinct values:
Comment.order(:post_id, created_at: :desc)
.pluck(Arel.sql('DISTINCT ON (post_id) id'))
DISTINCT ON is Postgres specific. The exact approach here will vary between RDBMS:es and there are many other alternatives such as lateral joins, window functions etc depending on your performance requirements.

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

say `Post` is a model, a class that inherits from `ApplicationRecord`, in rails. Then, what does Post.arel_table.create_table_alias does?

Lets say I have this code:
new_and_updated = Post.where(:published_at => nil).union(Post.where(:draft => true))
post = Post.arel_table
Post.from(post.create_table_alias(new_and_updated, :posts))
I have this code from a post about arel, but does not really explains what create_table_alias does. Only that at the end the result is an active activeRecord::Relation object, that is the result of the previously defined union. Why is needed to pass :posts, as a second param for create_table_alias, is this the name of the table in the database?
The Arel is essentially as follows
alias = Arel::Table.new(table_name)
table = Arel::Nodes::As.new(table_definition,alias)
This creates a SQL alias for the new table definition so that we can reference this in a query.
TL;DR
Lets explain how this works in terms of the code you posted.
new_and_updated= Post.where(:published_at => nil).union(Post.where(:draft => true))
This statement can be converted into the following SQL
SELECT
posts.*
FROM
posts
WHERE
posts.published_at IS NULL
UNION
SELECT
posts.*
FROM
posts
WHERE
posts.draft = 1
Well that is a great query but you cannot select from it as a subquery without a Syntax Error. This is where the alias comes in so this line (as explained above in terms of Arel)
post.create_table_alias(new_and_updated, :posts)
becomes
(SELECT
posts.*
FROM
posts
WHERE
posts.published_at IS NULL
UNION
SELECT
posts.*
FROM
posts
WHERE
posts.draft = 1) AS posts -- This is the alias
Now the wrapping Post.from can select from this sub-query such that the final query is
SELECT
posts.*
FROM
(SELECT
posts.*
FROM
posts
WHERE
posts.published_at IS NULL
UNION
SELECT
posts.*
FROM
posts
WHERE
posts.draft = 1) AS posts
BTW your query can be simplified a bit if you are using rails 5 and this removes the need for the rest of the code as well e.g.
Post.where(:published_at => nil).or(Post.where(:draft => true))
Will become
SELECT
posts.*
FROM
posts
WHERE
posts.published_at IS NULL OR posts.draft = 1
From the Rails official doc, from query method does this:
Specifies table from which the records will be fetched.
So, in order to fetch posts from the new_and_updated relation, we need to have an alias table which is what post.create_table_alias(new_and_updated, :posts) is doing.
Rubydoc for Arel's create_table_alias method tells us that the instance method is included in Table module.
Here :posts parameter is specifying the name of the alias table to create while new_and_updated provides ActiveRecord::Relation object.
Hope that helps.

Rails - How Do I do this Query? Get One of Each Record

I have a BlogPost model with a :category attribute. I am making a new BlogPost form, and I would like for the select menu to populate itself with each category entered in previous records by the user.
Therefore, I need one query to find all BlogPosts with a User ID, and then round up a list of each category they have entered. The same category will exist in multiple records, but of course I only want to return copy of it for the select menu.
Thank you :)
You can SELECT DISTINCT categories returned an INNER JOIN to the right user :
Category
.joins( :posts )
.where( posts: { user_id: current_user.id } )
.uniq
This should send a query like this :
SELECT DISTINCT categories.*
FROM categories
INNER JOIN posts ON posts.category_id = categories.id
WHERE posts.user_id = [whatever]
EDIT NOTE: be wary that uniq is both a method on a Relation and on an Array. Be sure to call it on the relation before it is cast to an array, or you will perform the uniq on an array of non-distinct results, which works too, but is absurd performance-wise.

How to write query in active record to select from two or more tables in rails 3

I don't want to use join
I want to manually compare any field with other table field
for example
SELECT u.user_id, t.task_id
FROM tasks t, users u
WHERE u.user_id = t.user_id
how can i write this query in Rails ??
Assuming you have associations in your models, you can simply do as follow
User.joins(:tasks).select('users.user_id, tasks.task_id')
you can also do as follow
User.includes(:tasks).where("user.id =tasks.user_id")
includes will do eager loading check the example below or read eager loading at here
users = User.limit(10)
users.each do |user|
puts user.address.postcode
end
This will run 11 queries, it is called N+1 query problem(first you query to get all the rows then you query on each row again to do something). with includes Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
Now when you do;
users = User.includes(:address).limit(10)
user.each do |user|
puts user.address.postcode
end
It will generate just 2 queries as follow
SELECT * FROM users LIMIT 10
SELECT addresses.* FROM addresses
WHERE (addresses.user_id IN (1,2,3,4,5,6,7,8,9,10))
Plus if you don't have associations then read below;
you should be have to look at http://guides.rubyonrails.org/association_basics.html
Assuming your are trying to do inner join, by default in rails when we associate two models and then query on them then we are doing inner join on those tables.
You have to create associations between the models example is given below
class User
has_many :reservations
...# your code
end
And in reservations
class Reservations
belongs_to :user
... #your code
end
Now when you do
User.joins(:reservations)
the generated query would look like as follow
"SELECT `users`.* FROM `users` INNER JOIN `reservations` ON `reservations`.`user_id` = `users`.`id`"
you can check the query by doing User.joins(:reservations).to_sql in terminal
Hopefully it would answer your question
User.find_by_sql("YOUR SQL QUERY HERE")
You can use as follows..
User.includes(:tasks).where("user.id =tasks.user_id").order(:user.id)

Resources