custom ordering activerecord data in rails - ruby-on-rails

I have 3 courses A(july 1), B(july 2), C(july 3).A and B is rated 4 and C is rated 5.
I want to order the course like this
C should come first because it was created latest and it has higher rating than others.
A should come second because it was created first than B
I cant use order because it wont give me what i need. any way to fix this?
Here is how i am fetching the data
#courses.order('updated_at DESC, average_rating DESC')
code
[
#<Course:0x00000009f3c128
id: 6,
tutor_id: 2,
course_name: "name",
course_subtitle: "sub",
course_description: "<p>test</p> test\r\n",
video_link: "https://www.youtube.com/watch?v=UVrQcieqD0U",
course_language: "German",
course_image: "finalse.png",
created_at: Tue, 11 Jul 2017 05:03:03 UTC +00:00,
updated_at: Tue, 11 Jul 2017 08:47:03 UTC +00:00,
status: "accepted",
average_rating: 2.5,
rated_time: nil>,
#<Course:0x00000008139608
id: 7,
tutor_id: 2,
course_name: "another",
course_subtitle: "another subtuitle",
course_description: "<p>course descrition</p>\r\n",
video_link: "https://www.youtube.com/watch?v=uaTeZA-Gj7s",
course_language: "Chinese",
course_image: nil,
created_at: Tue, 11 Jul 2017 10:40:45 UTC +00:00,
updated_at: Tue, 11 Jul 2017 10:41:06 UTC +00:00,
status: "accepted",
average_rating: 2.5,
rated_time: nil>,
#<Course:0x0000000813bea8
id: 8,
tutor_id: 2,
course_name: "asfas",
course_subtitle: "were",
course_description: "<p>asdfsadf</p>\r\n",
video_link: "https://www.youtube.com/watch?v=xGytDsqkQY8",
course_language: "English",
course_image: nil,
created_at: Wed, 12 Jul 2017 03:53:26 UTC +00:00,
updated_at: Wed, 12 Jul 2017 04:32:33 UTC +00:00,
status: "accepted",
average_rating: 1.0,
rated_time: nil>,

Try:
Course.all.order("average_rating DESC, created_at ASC")

try
Course.order({ created_at: :desc, rating: :desc })
This will sort first on created_at and if two records have same created_at the will sort on the basis of rating

Related

Rails merge common attributes on a `has_many: through` model to show unique values based on another attribute of the model

I have the following models & relationships:
class Company < ApplicationRecord
has_many :jobs, dependent: :delete_all
has_many :job_technologies, through: :jobs
end
class Job < ApplicationRecord
has_many :job_technologies
has_many :technologies, through: :job_technologies
belongs_to :company
end
class JobTechnology < ApplicationRecord
belongs_to :job
belongs_to :technology
end
Job technologies has this schema:
create_table "job_technologies", force: :cascade do |t|
t.bigint "job_id"
t.bigint "technology_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "title_matches"
t.integer "description_matches"
t.index ["job_id"], name: "index_job_technologies_on_job_id"
t.index ["technology_id"], name: "index_job_technologies_on_technology_id"
end
So when I create a JobTechnology I'm doing it like this:
JobTechnology.new(
job: Job.new(title: 'some title'),
technology: Technology.where(name: 'javascript').first,
title_matches: 1,
description_matches: 10
)
This means if I want to see what technologies a job has I can do:
Job.first.job_technologies
=> [#<JobTechnology:0x00007ffc5b5957e0
id: 647,
job_id: 263,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 0,
description_matches: 2>,
#<JobTechnology:0x00007ffc5b5953a8
id: 648,
job_id: 263,
technology_id: 4,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 1,
description_matches: 2>,
#<JobTechnology:0x00007ffc5b595100
This will always be a list of unique technologies because I'm only creating a JobTechnology for each unique technology (e.g. 'javascript', 'ruby', etc...) for each Job.
Now I want to see what technologies a company has. I can do:
Company.first.job_technologies
=> [#<JobTechnology:0x00007ffc56e3b9d0
id: 647,
job_id: 270,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 0,
description_matches: 2>,
#<JobTechnology:0x00007ffc56e3b908
id: 648,
job_id: 271,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 1,
description_matches: 2>,
#<JobTechnology:0x00007ffc56e3b818
id: 649,
job_id: 263,
technology_id: 31,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 0,
description_matches: 1>,
#<JobTechnology:0x00007ffc56e3b750
id: 650,
job_id: 263,
technology_id: 32,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 0,
description_matches: 2>]
The problem is that Company can have JobTechnologies that are 'duplicate' technologies (imagine a Company has 5 Jobs and they all have JobTechnology 'javascript'). You can see in above example we have
#<JobTechnology:0x00007ffc56e3b9d0
id: 647,
job_id: 270,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 0,
description_matches: 2>,
#<JobTechnology:0x00007ffc56e3b908
id: 648,
job_id: 271,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 1,
description_matches: 2>,
I want to merge the JobTechnology so that we only have one Technology counted for each technology, but I want to aggregate the title_matches and description_matches. So the above example would become
#<JobTechnology:0x00007ffc56e3b9d0
id: 647,
job_id: 263,
technology_id: 1,
created_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
updated_at: Mon, 31 May 2021 12:56:36 UTC +00:00,
title_matches: 1,
description_matches: 4>
I could do this as a method on the Company that merges these manually, but I'd need to do this manually every time we search for this. I'm wondering if there's a less expensive way to achieve this.
I don't know much about caching but perhaps the best option is to do that - any links/thoughts/advice on this appreciated. I just have a feeling that there's a way to do this through an active record query without having to manually calculate it each time.
Another thought I have is whether I should create a CompanyTechnology model and perhaps that's where this logic should live
Getting id, job_id, created_at and updated_at as you outlined in your example doesn't make much sense. If the result is aggregated through 5 JobTechnology-s, which id should it be? I assume you don't care about it anyway. Lets focus on the items you clearly defined, technology_id, title_matches and description_matches.
What you're looking for is called GROUP BY in SQL. The equivalent in rails is .group(). We want to group the results by technology_id and for each technology_id aggregate (sum) the title_matches and description_matches.
Company.first.job_technologies.group(:technology_id).pluck(
:technology_id, 'sum(title_matches)', 'sum(description_matches)'
)
This will return an array of arrays where every internal array has the 3 elements we're looking for.
To get rid of the sql literals we can use Arel for the same thing only safer.
Company.first.job_technologies.group(:technology_id).pluck(
:technology_id,
JobTechnology.arel_table[:title_matches].sum,
JobTechnology.arel_table[:description_matches].sum
)
If you want to get an activerecord relation instead of an array of arrays, use select instead of pluck. However, be careful with it because since it doesn't have an id column, it might act a bit weird.

Search for all records that have a particular value using case insensitive in a JSONB array field

I'm using postgres database and trying to query all records with "Value"=>"Black" in this JSONB field. That field contain an array of objects, e.g. {"id"=>"1", "key"=>"size", "value"=>"P"}
How do I query(case-insensitive) this records?
This is my code so far
def by_feature_value(value)
relation.where('features #> ?', [{ value: value }].to_json)
end
Records
#<ProductSku:0x000055de9cc01ba8
id: 33,
product_id: 3,
code: "1234",
ean: "12345",
created_at: Mon, 30 Apr 2018 11:47:00 UTC +00:00,
updated_at: Mon, 30 Apr 2018 11:47:00 UTC +00:00,
features: [{"id"=>"2", "key"=>"Color", "Value"=>"Black"}]>
#<ProductSku:0x000055de9cc01ba8
id: 33,
product_id: 3,
code: "1234",
ean: "12345",
created_at: Mon, 30 Apr 2018 11:47:00 UTC +00:00,
updated_at: Mon, 30 Apr 2018 11:47:00 UTC +00:00,
features: [{"id"=>"2", "key"=>"Color", "Value"=>"black"}]>,
The correct answer to this is
ProductSku.where('lower(features::text)::jsonb #> lower(?)::jsonb', [{ Value: value }].to_json)
Without type casting the first comment on your post does not work.

Rails: find attachments without user OR without comment

Attachment has_one :comment, optional: true
Attachment has_one :user, through :comment, comment - through task, task - through project
I want to find all current_user attachments plus attachments without owner -> no parent comment
Query Attachment.joins(comment: [task: [:project]]).where('projects.user_id = ? OR attachments.comment_id IS NULL', user.id) doesnt include attachments without comment, why?
[10] pry(#<CommentResource>)> Attachment.all
=> [#<Attachment:0x00000008a6f3a8
id: 1,
file: "attachments.rb",
comment_id: 1,
created_at: Sun, 21 May 2017 14:18:21 UTC +00:00,
updated_at: Sun, 21 May 2017 14:18:21 UTC +00:00>,
#<Attachment:0x00000008a6f268
id: 2,
file: "attachments.rb",
comment_id: 1,
created_at: Sun, 21 May 2017 14:18:21 UTC +00:00,
updated_at: Sun, 21 May 2017 14:18:21 UTC +00:00>,
#<Attachment:0x00000008a6f128
id: 3,
file: "attachments.rb",
comment_id: nil,
created_at: Sun, 21 May 2017 14:29:51 UTC +00:00,
updated_at: Sun, 21 May 2017 14:29:51 UTC +00:00>]
[11] pry(#<CommentResource>)> Attachment.joins(comment: [task: [:project]]).where('projects.user_id = ? OR attachments.comment_id IS NULL', user.id).to_sql
=> "SELECT \"attachments\".* FROM \"attachments\" INNER JOIN \"comments\" ON \"comments\".\"id\" = \"attachments\".\"comment_id\" INNER JOIN \"tasks\" ON \"tasks\".\"id\" = \"comments\".\"task_id\" INNER JOIN \"projects\" ON \"projects\".\"id\" = \"tasks\".\"project_id\" WHERE (projects.user_id = 1 OR attachments.comment_id IS NULL)"
[12] pry(#<CommentResource>)> Attachment.joins(comment: [task: [:project]]).where('projects.user_id = ? OR attachments.comment_id IS NULL', user.id)
=> [#<Attachment:0x0000000891c7a8
id: 1,
file: "attachments.rb",
comment_id: 1,
created_at: Sun, 21 May 2017 14:18:21 UTC +00:00,
updated_at: Sun, 21 May 2017 14:18:21 UTC +00:00>,
#<Attachment:0x0000000891c668
id: 2,
file: "attachments.rb",
comment_id: 1,
created_at: Sun, 21 May 2017 14:18:21 UTC +00:00,
updated_at: Sun, 21 May 2017 14:18:21 UTC +00:00>]
The joins method on active record queries the records by using inner join, which means in your case it only returns attachments with comments. If you want to include attachments without comments, you need to left outer join.
http://guides.rubyonrails.org/active_record_querying.html#left-outer-joins

How the find the records which are not duplicates

I have a table which will have possible duplicate records.
id: 24,
name: "vamsi",
mobile: "7639817688",
company: "digi",
requirement: "mobile app",
created_at: Wed, 12 Oct 2016 11:05:33 UTC +00:00,
updated_at: Wed, 12 Oct 2016 11:05:33 UTC +00:00,
email_sent: false>,
#<Contact:0x00000006d7a4f0
id: 25,
name: "vamsi",
mobile: "7639817688",
company: "digi",
requirement: "mobile app",
created_at: Wed, 12 Oct 2016 11:05:57 UTC +00:00,
updated_at: Wed, 12 Oct 2016 11:05:57 UTC +00:00,
email_sent: false>]
Now I would like to find the unique records on which email_sent is false. I have tried this
Contact.where(email_sent: false).distinct
Contact Load (0.4ms) SELECT DISTINCT "contacts".* FROM "contacts" WHERE "contacts"."email_sent" = $1 [["email_sent", false]]
=> [#<Contact:0x00000006a1a698
id: 25,
name: "vamsi",
mobile: "7639817688",
company: "digi",
requirement: "mobile app",
created_at: Wed, 12 Oct 2016 11:05:57 UTC +00:00,
updated_at: Wed, 12 Oct 2016 11:05:57 UTC +00:00,
email_sent: false>,
#<Contact:0x00000006a1a418
id: 24,
name: "vamsi",
mobile: "7639817688",
company: "digi",
requirement: "mobile app",
created_at: Wed, 12 Oct 2016 11:05:33 UTC +00:00,
updated_at: Wed, 12 Oct 2016 11:05:33 UTC +00:00,
email_sent: false>]
But I would not want 2 records, since both are same. I would like only one to be shown. Is there any way I can solve this.
First part, the model should have validations so that these kinds of data won't be stored
It can be done via this
validates_uniqueness_of :name, scope: [:mobile, :requirement, :company]
Second part, still if you want to query something like above scenario. You have to do this
Contact.select(:name, :company, :mobile, :requirement).where(email_sent: false).distinct would be the query
ps: Answer picked up from all the comments in the question
Try group by email_sent after using where:
Contact.where(email_sent: false).group(:email_sent)

How to group date by day and sum in postgres?

I got this model:
[#<Account:0x007fcf32153098
id: 1,
profit: 100,
user_id: 1,
created_at: Sun, 15 Nov 2015 02:27:43 UTC +00:00,
updated_at: Sun, 15 Nov 2015 02:27:43 UTC +00:00>,
#<Account:0x007fcf32152df0
id: 2,
profit: 500,
user_id: 1,
created_at: Sun, 16 Nov 2015 15:05:07 UTC +00:00,
updated_at: Sun, 15 Nov 2015 15:05:07 UTC +00:00>,
]
And for now I got this to group them in date:
Account.all.group_by{|a| a.created_at.strftime("%Y-%m-%d")}
{"2015-11-15"=>
[#<Account:0x007fcf3247b1a8
id: 1,
profit: 100,
user_id: 1,
created_at: Sun, 15 Nov 2015 02:27:43 UTC +00:00,
updated_at: Sun, 15 Nov 2015 02:27:43 UTC +00:00>],
"2015-11-16"=>
[#<Account:0x007fcf3247afc8
id: 2,
profit: 500,
user_id: 1,
created_at: Sun, 16 Nov 2015 15:05:07 UTC +00:00,
updated_at: Sun, 15 Nov 2015 15:05:07 UTC +00:00>]}
My question is: How can I group them and at the same time sum the profit together if there are more than one record for that day? Seems like I can't use sum(:profit) with postgres?
I think you can just do this simply with:
Account.order("DATE(created_at)").group("DATE(created_at)").sum(:profit)

Resources