Rails where clause to select by specific attribute - ruby-on-rails

I have the following query:
I have the following model:
2.1.5 :025 > OnlineCourseRegistration.last
=> #<OnlineCourseRegistration id: 14392, cart_id: 15177, user_id: 7133, course_class_id: 681, created_at: "2017-10-29 23:28:45", updated_at: "2017-10-30 20:18:53", exam_attempts: 0, exam_completed_at: nil, evaluation_completed_at: nil, status: "Active", score: "", add_extension: false, retest_cart_id: nil, retest_purchased_at: nil>
...and I am running this query:
registrations = OnlineCourseRegistration.where(course_class_id: 123).where(status: "Completed").where("score >= ?", 80)
It is possible that more than one record for the same user_id can be returned. If this is the case I would like to only return the last record...or the record with the latest :exam_completed_at date for that user.
For context here is the entire loop:
registrations = OnlineCourseRegistration.where(course_class_id: 123).where(status: "Completed").where("score🏸 >= ?", 80)
if !registrations.empty?
registrations.each do |b|
email_recipients << b
end
I build up the email_recipients array, then hand that off to a mailer. The issue I am having is that if the user gets returned twice, then they are getting two emails. I only want them to get a single email, so I would like on the the last record returned (or the record with the most recent exam_completed_at date.

Here is what I came up with:
registrations = OnlineCourseRegistration.where(course_class_id: 842).where(status: "Completed").where("score🏸 >= ?", 80).order(exam_completed_at: :desc)
Note I added an order clause above, then...
registrations = registrations.uniq_by {|u| u.user_id}
I am unsure if uniq_by removes the first or last record, but this in combination with the additional order clause seems to do the trick.

Related

How to order by descending value in controller?

I am trying to show the list of jobs ordered by median_salary by descending order. So far, it seems to only take into account the first number of median_salary. So something like 900 is listed above 1000, even though the value of 1000 > 900.
homes_controller.rb:
def index
nyc_highest = Highestpaidjob.where("city = ?", "NYC")
#nyc_highest = nyc_highest.order("median_salary DESC")
end
index.html.erb:
<%= #nyc_highest.inspect %>
returns:
#<ActiveRecord::Relation [#<Highestpaidjob id: 11, job: "Architect, City Planner", median_salary: "95928.48", count: 237, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:17:17", updated_at: "2016-07-25 18:17:17">, #<Highestpaidjob id: 7, job: "Medical", median_salary: "170507.69", count: 128, margin_of_error: "", city: "NYC", state: "New York", created_at: "2016-07-25 18:09:30", updated_at: "2016-07-25 18:09:30">]>
It is listing 95928.48 as higher than 170507.69. Am I missing a condition?
I've looked at Best way to implement sort asc or desc in rails and it seemed to suggest the way I am currently writing the sort.
It's because your median_salary database field is string and it's sorted as string. You need to cast it to integer in order clause, or create a migration, which will change field datatype.
Difference between strings being sorting and floats being sorted:
irb(main):001:0> ["95928.48", "170507.69"].sort
=> ["170507.69", "95928.48"]
irb(main):002:0> [95928.48, 170507.69].sort
=> [95928.48, 170507.69]
In postgres your order clause should looks like this:
#nyc_highest = nyc_highest.order("CAST(median_salary as FLOAT) DESC")
As #teksisto said, you should change the median_salary for float or some type that accepts decimals. Also, I would suggest to create a scope on your model, something like
scope :nyc_highest, -> { where("city = ?", "NYC").order("median_salary DESC") }
on your Highestpaidjob model. Then, you just call Highestpaidjob.nyc_highest in any place of your application you like.
For changing the median_salary data type:
rails g migration ChangeMedianSalaryType
then edit your migration file:
class ChangeMedianSalaryType < ActiveRecord::Migration
def up
change_column :highestpaidjobs, :median_salary, :float
end
def down
change_column :highestpaidjobs, :median_slary, :string
end
end

Is there a guarantee that ActiveRecord returns objects ordered by ID?

My project is hosted on Heroku.
I was surprised when Room.all method returned objects with first object with ID 2 and only then a second object with ID 1. I thought that there were some sort of guarantee that objects are returned already ordered by ID. Should I always call Room.all.order(:id) instead of regular all method?
irb(main):002:0> Room.all
=> #<ActiveRecord::Relation [
#<Room id: 2, color: "rgb(83, 180, 83)", status: "Status #2", created_at: "2014-10-11 14:14:02", updated_at: "2014-10-11 14:18:19">,
#<Room id: 1, color: "rgb(0, 96, 255)", status: "Status #3", created_at: "2014-10-11 14:14:02", updated_at: "2014-10-11 14:18:30">
]>
Nope. Room.all just ends up with the SQL SELECT * FROM rooms; - no order is there. In this event, the order of the records is determined by the database (for instance, in PostgreSQL, I notice it returns me the most recently updated records last).
If you want to ensure there's an order when you call .all, add a default scope which adds it:
default_scope order('rooms.id ASC')

Why do I keep getting WhereChain Query due to scope?

So i'm trying to create a scope that takes two variables, (current_user, other_user) and seperate the messages between the two based off of sender_id and receiver_id (my foreign_keys).
I've used the following before on a project, and it worked well:
scope :between, -> (me, other) { Message.where{((:sender_id == my{me.id}) & (:receiver_id == my{other.id})) | ((:sender_id == my{other.id}) & (:receiver_id == my{me.id}))}}
However, on my current project, I use the same thing, and it's doing two things.
1: After binding.pry it provides this response.
[1] pry(#<ConversationsController>)> #messages
Message Load (0.4ms) SELECT "messages".* FROM "messages"
=> #<ActiveRecord::QueryMethods::WhereChain:0x00000106d83c98
#scope=
[#<Message id: 1, title: nil, body: "Message 1", sender_id: 11, receiver_id: 1, created_at: "2014-05-20 19:29:34", updated_at: "2014-05-20 19:29:34">,
#<Message id: 2, title: nil, body: "Message 2", sender_id: 1, receiver_id: 11, created_at: "2014-05-20 19:30:15", updated_at: "2014-05-20 19:30:15">,
#<Message id: 3, title: nil, body: "radda?", sender_id: 1, receiver_id: 109, created_at: "2014-05-20 19:30:39", updated_at: "2014-05-20 19:30:39">]>
As you can see above, it's combining all user id's, not just the sender and receiver. So all users receive all messages. And then, I keep getting this WhereChain Error.
#<ActiveRecord::QueryMethods::WhereChain:0x00000107f99ea0>
Please help me understand what I may be doing wrong, or what I could do right. Thank you.
Try:
scope :between, -> (me, other) { where(['(sender_id = :me and receiver_id = :other) or (sender_id = :other and receiver_id = :me)', {me: me.id, other: other.id}])}
You do not have squeel gem installed in your current project and the syntax you are using requires it. In Rails 3 you would receive an error wrong number of arguments 0 for 1, however in rails 4 you can call where without any params (so you can call not). The block you passed has been ignored and WhereChain object is returned.
Note that squeel is no longer maintained. Use with care.

Deleting records from HABTM association

I'm trying to do something fairly simple. I have two models, User and Group. For simplicity's sake, let's say they look like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
end
and
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
end
Now, for some reason, I have a user that has the same group twice. In the Rails Console:
user = User.find(1000)
=> #<User id: 1000, first_name: "John", last_name: "Doe", active: true, created_at:
"2013-01-02 16:52:36", updated_at: "2013-06-17 16:21:09">
groups = user.groups
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">, #<Group id: 1, name: "student", is_active: true,
created_at: "2012-12-24 15:08:59", updated_at: "2012-12-24 15:08:59">]
user.groups = groups.uniq
=> [#<Group id: 1, name: "student", is_active: true, created_at: "2012-12-24 15:08:59",
updated_at: "2012-12-24 15:08:59">]
user.save
=> true
And there is some SQL output that I've silenced. I would think that everything should be all set, but it's not. The groups aren't updated, and that user still has both. I could go into the join table and manually remove the duplicates, but that seems cludgy and gross and unnecessary. What am I doing wrong here?
I'm running Rails 3.2.11 and Ruby 1.9.3p392
Additional note: I've tried this many different ways, including using user.update_attributes, and using group_ids instead of the groups themselves, to no avail.
The reason this doesn't work is because ActiveRecord isn't handling the invalid state of duplicates in the habtm association (or any CollectionAssociation for that matter). Any ids not included in the newly assigned array are deleted - but there aren't any in this case. The relevant code:
# From lib/active_record/associations/collection_association.rb
def replace_records(new_target, original_target)
delete(target - new_target)
unless concat(new_target - target)
#target = original_target
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
target
end
The 'targets' being passed around are Arrays of assigned records. Note the call to delete(target - new_target) is equivalent in your case to delete(user.groups - user.groups.uniq) which results in an empty Array passed (since comparison is based on the id attribute of each record).
Instead, you'll need to clear out the association and then reassign the single group again:
group = user.groups.first
user.groups.clear
user.groups << group
This might be a way to cleanup those duplicates (it handles any number of groups of duplicate associations):
user = User.find(1000)
user.groups << user.groups.group_by(&:id).values.find_all {|v| v.size > 1}.each {|duplicates| duplicates.uniq_by! {|obj| obj.id}}.flatten.each {|duplicate| user.groups.delete(duplicate)}

Sorting local variable

My Rails app is getting the history of changes for two models, using the auditor gem like so:
#audit = Audit.where( :auditable_id => current_user.posts,
:auditable_type => "Post") +
Audit.where( :auditable_id => #comments,
:auditable_type => "Comment")
This works, but then I need to sort the whole #audit variable by the time the change was made.
I have two issues to solve.
the following methods have not worked: sort, sort_by, order
I need to figure out which of the following fields I need to sort by:
=> Audit(id: integer, auditable_id: integer, auditable_type: string, owner_id: integer, owner_type: string, user_id: integer, user_type: string, action: string, audited_changes: text, version: integer, comment: text, **created_at**: datetime)
1.9.3-p194 :002 > Audit.last
Audit Load (168.0ms) SELECT "audits".* FROM "audits" ORDER BY version DESC, created_at DESC LIMIT 1
=> #<Audit id: 5, auditable_id: 58, auditable_type: "Post", owner_id: 58, owner_type: "Post", user_id: 1, user_type: "User", action: "update", audited_changes: {"status"=>["to approve", "to review"], " **updated_at** "=>[2012-08-24 15:29:26 UTC, 2012-08-24 19:29:52 UTC]}, version: 2, comment: "post modified by Bruno Amaral ", created_at : "2012-08-24 19:29:52">
You should be able to build a single query to load all of the Audit objects you're interested in. Since it's a single query, the database can handle the sorting too.
The SQL you want to execute looks something like this:
SELECT *
FROM audits
WHERE
auditable_type = 'Post' AND auditable_id = … OR
auditable_type = 'Comment' AND auditable_id IN (…)
ORDER BY created_at
Which you should be able to build using Arel with something like this (assuming you're using Rails 3):
t = Audit.arel_table
for_post = t[:auditable_type].eq('Post').and(t[:auditable_id].eq(post.id))
for_comments = t[:auditable_type].eq('Comment').and(t[:auditable_id].in(comment_ids))
audits = Audit.where(for_post.or(for_comments)).order(:created_at)
For more information about building complex queries with Arel, see ASCIIcasts episode 215 and the Arel README.
If you don't like the Arel syntax, you can also use find_by_sql or pass a string to where, but bear in mind that using Arel will shield you from various kinds of errors and subtle difference between databases.
#audit = Audit.where( "auditable_id IN (?) OR auditable_id IN (?)",
current_user.posts,
#comments ).order_by( :created_at )

Resources