Ordering Records with Same Date Using Second Field - ruby-on-rails

I have an application with many Galleries. Each gallery has a start_date DateTime field.
For legacy reasons, the time of all start_dates is midnight.
Thu, 10 Jul 2014 00:00:00 UTC +00:00
I need to order the galleries by date, so a user can shuffle back and forth across them using a 'older' and 'newer' link. Galleries are ordered based on start_date:
scope :start_date_ascending, -> { order(start_date: :asc) }
scope :start_date_descending, -> { order(start_date: :desc) }
My problem is that there is no clear older or newer gallery when there are multiple galleries with the same date. In such cases I cannot predict the order that Galleries with the same date are returned in, so moving across multiple galleries with the same date becomes random and error-prone.
I have scope set up to find newer and older galleries:
scope :newer_than, -> (gallery){ where.not(id:gallery).where('start_date >= :gallery_start_date', gallery_start_date:gallery.start_date) }
scope :older_than, -> (gallery){ where.not(id:gallery).where('start_date < :gallery_start_date', gallery_start_date:gallery.start_date) }
And I find the next and previous galleries like this:
def self.next_newer(gallery)
Gallery.newer_than(gallery).start_date_ascending.limit(1).first
end
def self.next_older(gallery)
Gallery.older_than(gallery).start_date_descending.limit(1).first
end
So it seems to me I need a secondary way to order the galleries that are the same date. It doesn't matter what order that is - it could simply be their ids.
How can I handle this situation so that galleries with the same date appear in the query in a predictable, fixed order so that next_newer and next_older move through them?

Perhaps you can sort with a second criteria, name for example and if available, or even id
scope :start_date_ascending, -> { order(start_date: :asc, name: :asc) }
scope :start_date_descending, -> { order(start_date: :desc, name: :asc) }
note on the start_date_descending scope it can be nice to keep name asc, so despite of the descending date order we keep a alphabetical order
And for the next and previous gallery, if you can store an array, you can get your ordered ids and iterate through them
ids = Gallery.start_date_ascending.pluck :id

Based on #BengaminSinclaire's suggestion:
def self.next_newer(gallery)
ascending_ids = Gallery.start_date_ascending.pluck :id
current_index = ascending_ids.index(gallery.id)
Gallery.find(ascending_ids[current_index+1]) if current_index < ascending_ids.length - 1
end
def self.next_older(gallery)
ascending_ids = Gallery.start_date_ascending.pluck :id
current_index = ascending_ids.index(gallery.id)
Gallery.find(ascending_ids[current_index-1]) if current_index > 0
end

Related

Exclude records before a certain date

I cannot seem to get the syntax correct on this. What I need to is grab all records after Feb 18 2021.
I feel like I have tried a million things so far but this is what I have at the moment which isn't working.
#exclusion_date = Date.parse('2021-02-18 00:00:01')
#surveys = Survey.closed.where('survey_type = ? AND created_at < ?','individual', #exclusion_date).order(created_at: :desc)
This still returns the exact same data as it did before I tried to exclude the older records.
You want to load records created after a specific time. That means the timestamp must be greater than the specific date. This should work:
#exclusion_date = Time.parse('2021-02-18 00:00:01')
#surveys = Survey
.closed
.where('survey_type = ? AND created_at > ?', 'individual', #exclusion_date)
.order(created_at: :desc)
In newer versions of Rails you can do this with a clever syntactic sugar using infinite ranges #exclusion_date..., meaning all possible future dates.
#exclusion_date = Time.parse('2021-02-18 00:00:01')
#surveys = Survey
.closed
.where(survey_type: 'individual')
.where(created_at: #exclusion_date...)

how can I paginate records not by N value quantity, but by created_at.day?

I have a table Post.
Columns: created_at, title, body
Default pagination behaviour is to show n items per page and than add link_to prev/next.
How can I paginate not by n items, but by created_on a date?
I've figured out the gem https://github.com/ankane/groupdate, but it only helps to group, not to do the pagination.
In this case, it sound like you don't want pagination, you want query filtering based on a bucketed value. You will first need a distinct list of post days. You may need to use some database-specific query features to get that from timestamps. For example, with Postgres, you can use the date() function to extract just the date portion from your created_at timestamp, and we can get a distinct list of them:
dates = Post.pluck("distinct date(created_at)").sort
Note that this will induce a full table scan, so it'll be slow for a large number of posts.
How you use that list of dates is up to you. You might choose to render a list of them as links for the user to click, for example. You might then have your index method accept a date param, then use that to find posts on that date:
def index
scope = Post.all
if params[:date].present?
date = DateTime.parse(params[:date])
scope = scope.
where("created_at >= ? AND created_at <= ?",
date.beginning_of_day, date.end_of_day).
order("created_at ASC")
end
# For the given date, paginate the posts on that date in pages of 25
#posts = scope.page(1).per(25)
end

ActiveRecord order a relation with nulls last and non-null values sorted by secondary attribute

I've got a model, Prospect. I want to sort prospects by some value (e.g. updated_at ASC), however I also want to bisect the sorted list so that all prospects where lead_id IS NOT NULL are shown first, sorted by updated_at ASC, then all prospects where lead_id IS NULL are shown last sorted by updated_at ASC. I don't care what lead_id is, only that any records where lead_id IS NOT NULL are bumped to the beginning of the list. I need to be able to paginate the dataset and the query needs to be relatively efficient (turnaround time should be well under 500ms, ideally under 100ms).
First Approach
I first tried to accomplish this using the following query, however this doesn't work (for my use case) because prospects are sorted by lead_id (as you would expect from this query), which is unique, therefore making a secondary sort effectively useless.
Prospect.order("lead_id ASC nulls last, updated_at ASC")
Second Approach
I tried a (slightly modified) approach suggested by Andrey Deineko. This returns the entire dataset in the correct order, but there is no way to merge the two separate relations into a single relation that can be paginated. As a result, in order to paginate the dataset, I need to instantiate every row from the table into memory. That would be acceptable for a few dozen records at most, but certainly not 20k+.
# prospect.rb
scope :with_leads, -> { where.not(lead_id: nil) }
scope :without_leads, -> { where(lead_id: nil) }
scope :ordered, -> { order(:total_clicks_count) }
[Prospect.with_leads.ordered, Prospect.without_leads.ordered].flatten
Third Approach
I realized that I can get a sorted list of prospect ids (both with and without a lead_id) and use that to get the full dataset ordered by id. This accomplishes what I need and works fine for a few dozen or hundred records, but isn't viable for 20k+ records.
lead_ids = Prospect.where.not(lead_id: nil).pluck(:id)
prospect_ids = Prospect.where(lead_id: nil).pluck(:id)
prospects = Prospect.order_by_ids([lead_ids, prospect_ids].flatten)
Here is the source for order_by_ids:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.order_by_ids(ids)
# https://stackoverflow.com/a/26777669/4139179
order_by = ["CASE"]
ids.each_with_index do |id, index|
order_by << "WHEN #{self.name.underscore}s.id='#{id}' THEN #{index}"
end
order_by << "END"
order(order_by.join(" "))
end
end
The problem with this second approach is that it takes up to 1 second per 1000 records to build the list or ordered ids for each subset (prospects with and without a lead_id) and then use that to fetch the entire dataset in order.
Is there a better approach that will return the entire dataset (in a way that can be paginated) ordered by some attribute (e.g. updated_at ASC) where prospects with a lead_id are at the top of the list and those without are at the bottom?
When using ORDER BY "column" in PostgreSQL, NULL values will come last by default. So
Prospect.order(:lead_id, :updated_at)
should do the trick.
Your actual need:
The effect in practice would be that when a user views a list of their
sales prospects, those that have been converted to leads will show up
first in the list, even if the list is sorted by other attributes.
# prospect.rb
scope :with_leads, -> { where.not(lead_id: nil) }
scope :without_leads, -> { where(lead_id: nil) }
scope :ordered, -> { order(:total_clicks_count) }
And then use these scopes to present to the user:
Prospect.with_leads.ordered.each do
#...
end
Prospect.without_leads.ordered.each do
#...
end

Rails Model Next/Prev Navigation Scope with datetime field

I have a model lets say Post which has a column called published_at (datetime). My model has two instance methods called next and prev.
I'm using mysql as a database adapter.
def next
self.class.unscope(:order).online
.where('published_at > ?', published_at)
.order('published_at ASC').first
end
def prev
self.class.unscope(:order).online
.where('published_at < ?', published_at)
.order('published_at DESC').first
end
With the code above the navigation works if every post has another date. If some posts are on the same date just one record of those gets shown.
Goal: Get the next or previous record ordered by published_at datetime column.
Any ideas?
Neither "next" , nor "previous" make sense in SQL. You have to explicitly define sort order by uniq key.
If you have non-uniq column as sort base, you have to add uniq one, id for example.
Then you have to change WHERE clause to accept rows wich have same value for your non-uniq column:
def next
condition = <<~SQL
(published_at = :published_at AND id > :id) OR
(published_at > :published_at)
SQL
self.class.unscope(:order).
online.
where(condition, published_at: published_at, id: id).
order(published_at: :asc, id: :asc).
take
end
prev is constructed similarly.

Rails 3 complex query with sort

I have a User model and an Event model. User has_many events. What I want to be able to do is find the User that has the most events created in the last 24 hours.
Ideally, the output format would have be an array of hashes [{User => [Event, Event, ...]}], which would be sorted by User's with the highest events.count
Thanks
This works for me, but might require tweaking depending on your database.
class User
scope :by_most_events, -> {
joins(:events)
.select("users.*, count(events.id) as event_count")
.where(events: { created_at: 24.hours.ago..Time.now })
.group("users.id, events.id") # this is for postgres (required group for aggregate)
.order("event_count desc")
}
end
### usage
User.by_most_events.limit(1).first.event_count
# => 123
As Sixty4Bit mentioned, you should use counter_cache, but it will not suffice in this situation because you need to query for events created in the last 24 hours.

Resources