Comparing ranges of datetimes in Ruby on Rails against one-another - ruby-on-rails

Right now I'm trying to see if a certain show's start and end times overlap another show that's currently recording => true where 'show' is the TV show the user wants to record.
def self.record_show
shows = Box.first.shows.where(:recording => true).flatten
show_start_and_end_times = shows.collect {|x| x.start_time..x.end_time}
current_show_time = show.start_time..show.end_time
overlap = show_start_and_end_times.select {|c| current_show_time.overlaps?(c)}
if overlap.present?
nil
else
show.update_attributes(:recording => true)
show.save
end
end
It runs the method, but I'm having difficulty figuring out how to get it so that it finds the actual currently recording show that's causing the overlap. So for example, let's say in 'shows' I currently have two shows:
[#<Show id: 181, box_id: 78, title: "The Fox", channel: 22, single_recording: true, created_at: "2014-08-12 19:55:49", updated_at: "2014-08-12 20:09:24", start_time: "2014-08-12 19:55:49", end_time: "2014-08-12 20:25:49", recording: true>, #<Show id: 186, box_id: 78, title: "Funniest Home Videos", channel: 45, single_recording: true, created_at: "2014-08-12 19:55:49", updated_at: "2014-08-12 20:09:27", start_time: "2014-08-12 23:20:49", end_time: "2014-08-13 00:20:49", recording: true>]
In show_start_and_end_times I have:
[Tue, 12 Aug 2014 19:55:49 UTC +00:00..Tue, 12 Aug 2014 20:25:49 UTC +00:00, Tue, 12 Aug 2014 23:20:49 UTC +00:00..Wed, 13 Aug 2014 00:20:49 UTC +00:00]
In current_show_time I have:
Tue, 12 Aug 2014 19:55:49 UTC +00:00..Tue, 12 Aug 2014 20:55:49 UTC +00:00
Which means that in overlap I have the start_time..end_time of the first show_start_and_end_times show, which is the one that is causing the overlap:
[Tue, 12 Aug 2014 19:55:49 UTC +00:00..Tue, 12 Aug 2014 20:25:49 UTC +00:00]
I tried comparing the two times against one-another:
(shows.first.start_time..shows.first.end_time) == (overlap.first)
Which gives me false, even though the times are exactly the same. How can I compare the overlap time against the shows list to figure out which show is causing the overlap?

You'll wand to check out:
http://guides.rubyonrails.org/active_record_querying.html
Using info from that you might be able to do something like:
def self.record_show
overlapping_shows = Box.first.shows.where(recording: true).where("start_time <= :show_end AND end_time >= :show_start", {show_start: show.start_time, show_end: show.end_time}).flatten
if overlapping_shows.present?
nil
else
show.update_attributes(:recording => true)
show.save
end
end

This is a pretty common problem, and I'd recommend checking out this SO question for general algorithm advice on how to do the overlap checking:
Determine Whether Two Date Ranges Overlap
UPDATE:
I would change it to something like this:
shows = Box.first.shows.where(:recording => true).flatten
overlapping_show = nil
current_show_time = show.start_time..show.end_time
shows.each do |s|
if current_show_time.overlaps?(s.start_time..s.end_time)}
overlapping_show = s
break # you could alternatively return an array of overlapping
# if you anticipate more than 1 will overlap
end
end
if overlap.present? #...

Related

Rails, how to sort a collection keeping a specific value at the end

I have an active record collection of some objects...
#<CoverElement:0x00007f87a4d78718
id: 312,
title: "Title 1",
link: "",
coverable_id: 35001,
coverable_type: "Article",
created_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
updated_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
cover_id: 4,
format: "small",
custom_image: nil,
position: 2,
second_title: nil,
second_title_url: nil>,
#<CoverElement:0x00007f87a4d6fde8
id: 313,
title: "Title 2",
link: "",
coverable_id: 35010,
coverable_type: "Article",
created_at: Thu, 07 May 2020 16:55:00 CEST +02:00,
updated_at: Sun, 22 Nov 2020 19:33:39 CET +01:00,
cover_id: 4,
format: "horizontal",
custom_image: nil,
position: 3,
second_title: nil,
second_title_url: nil>
For the desktop version of the website I use the position as order, but I need another order for the mobile version.
I need to sort by format and "small" must be the last.
Basically formats can be: "large", "vertical", "horizontal" and "small".
I try with
#elements.reorder('format ASC')
and it works but of course it keep the order "horizontal", "large", "vertical" and "small"
Every object with "small" as format must be at the end. Is there a way to do this or I have to write a custom sort method?
you will need another sort method for the mobile version. However if you sort by format, it will do an alphanumeric sort, which will not give you the results you want ("small" is last).
There are a couple of ways to get the order you want:
first way is to recast the format column as an enum, so that the database contains integer values iso strings:
class CoverElement < ActiveRecord::Base
enum format: [ :large, :vertical, :horizontal, :small ]
end
if this is not possible, you can do it with an sql snippet something like this:
order = <<-ORD.squish
CASE
WHEN 'format'='large' THEN 4
WHEN 'format'='vertical' THEN 3
WHEN 'format'='horizontal' THEN 2
WHEN 'format'='small' THEN 1
END
ORD
CoverElement.order(order)

Using Acts As Votable for multiple database tables with Rails 5

I have been using https://github.com/ryanto/acts_as_votable gem as a Save button for Posts. It has been all good so far. 👍
However now I created a separate scaffold (Articles) and wanted to add the same Save button. So users can save Posts and Articles, then view in their profiles.
Now I got problem as some Article records has same id as Post records. Plus how do I even display Saved records now as I dont know what id comes from Article or Post. 🤔
Is there any way to solve this with Acts As Votable Gem?
Thank you! 🙏
The current version (0.12.0) of acts_as_voteable does this out of the box. The Vote model has a column votable_type which can be a reference to multiple models.
#<ActsAsVotable::Vote:0x00007f9f6558a9b0
id: 4,
votable_type: "Post",
votable_id: 1,
voter_type: "User",
voter_id: 2,
vote_flag: true,
vote_scope: "save",
vote_weight: 1,
created_at: Mon, 31 Dec 2018 13:39:34 UTC +00:00,
updated_at: Mon, 31 Dec 2018 13:39:34 UTC +00:00>,
#<ActsAsVotable::Vote:0x00007f9f6558a4d8
id: 5,
votable_type: "Article",
votable_id: 3,
voter_type: "User",
voter_id: 2,
vote_flag: true,
vote_scope: "article",
vote_weight: 1,
created_at: Tue, 01 Jan 2019 15:15:27 UTC +00:00,
updated_at: Tue, 01 Jan 2019 15:15:27 UTC +00:00>
To display saved records you can use a scope like
#user.votes.for_type(Post)
#user.votes.for_type(Article)
I hope this answers your question.

is it possible to override built-in Ruby methods?

I am working on a problem where I have to pass an rpsec test. The problem is that the method is using the same name as a built in ruby method .count
given that I cannot change the rspec test, is it possible to override .count to behave differently? if not, is there a better way to get around this?
here is the rspec test I am trying to pass
subject = FinancialSummary.one_day(user: user, currency: :usd)
expect(subject.count(:deposit)).to eq(2)
my code:
class FinancialSummary
def self.one_day(user: user, currency: currency)
one_day_range = Date.today.beginning_of_day..Date.today.end_of_day
find_transaction(user.id, currency).where(created_at: one_day_range)
end
def self.find_transaction(user_id, currency)
Transaction.where(user_id: user_id,
amount_currency: currency.to_s.upcase
)
end
end
output:
[#<Transaction:0x00007f9b39c2e9b8
id: 1,
user_id: 1,
amount_cents: 1,
amount_currency: "USD",
category: "deposit",
created_at: Sat, 10 Mar 2018 18:46:53 UTC +00:00,
updated_at: Sat, 10 Mar 2018 18:46:53 UTC +00:00>,
#<Transaction:0x00007f9b3d0dbc38
id: 2,
user_id: 1,
amount_cents: 2000,
amount_currency: "USD",
category: "deposit",
created_at: Sat, 10 Mar 2018 18:47:43 UTC +00:00,
updated_at: Sat, 10 Mar 2018 18:47:43 UTC +00:00>,
#<Transaction:0x00007f9b3d0b3fa8
id: 7,
user_id: 1,
amount_cents: 1200,
amount_currency: "USD",
category: "withdraw",
created_at: Mon, 05 Mar 2018 02:22:42 UTC +00:00,
updated_at: Tue, 06 Mar 2018 18:48:20 UTC +00:00>]
it is printing out, what I believe to be the correct information, up until the test attempts to count the transactions by their category: 'deposit'. Then I get this error message:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: deposit: SELECT COUNT(deposit) FROM "transactions" WHERE "transactions"."user_id" = ? AND "transactions"."amount_currency" = ?
EDITED FOR MORE INFO
Some Assumptions Were Made in the Writing of this answer and modifications may be made based on updated specifications
Overriding count is a bad idea because others who view or use your code will have no idea that this is not the count they know and understand.
Instead consider creating a scope for this like
class FinancialSummary < ApplicationRecord
scope :one_day, ->(user:,currency:) { where(user: user, currency: currency) } #clearly already a scope
scope :transaction_type, ->(transaction_type:) { where(category: transaction_type) }
end
then the test becomes
subject = FinancialSummary.one_day(user: user, currency: :usd)
expect(subject.transaction_type(:deposit).count).to eq(2)
SQL now becomes:
SELECT COUNT(*)
FROM
"transactions"
WHERE
"transactions"."user_id" = ?
AND "transactions"."amount_currency" = "usd"
AND "transactions"."category" = "deposit"
Still very understandable and easy to read without the need to destroy the count method we clearly just used.
It's not clear what object the count message is being sent to because I don't know what FinancialSummary.one_day(user: user, currency: :usd) returns, but it seems like you are saying count is a method on whatever it returns, that you can't change. What does FinancialSummary.one_day(user: user, currency: :usd).class return?
Perhaps one solution would be to alias it on that object by adding alias_method :count, :account_count and then in your test calling expect(subject.account_count(:deposit)).to eq(2)
It would be easier if you could post the FinancialSummary#one_day method in your question.

Rails, how to get hours of a datetime in the correct time zone?

I have an offer model. and when I access it from the command line, it is displayed in the following format:
1.9.3p392 :058 > o = Offer.last
=> #<Offer id: 15, title: "Testing", valid_from: "2013-04-12 09:00:00", valid_until: "2013-04-12 14:00:00", created_at: "2013-04-12 18:31:54", updated_at: "2013-04-12 18:31:54">
In this output, valid_from and valid_until are formatted as I would expect. however, when I access them directly, I get something different:
1.9.3p392 :059 > o.valid_from
=> Fri, 12 Apr 2013 05:00:00 EDT -04:00
The issue comes with this function I have inside of app/models/offer_model.rb :
def start_hour
self.valid_from.strftime('%l').to_i unless self.valid_from.nil?
end
When I run this command, with the model above, the output is 5. I would expect it to return 9 in this case. How can I get start_hour to return the hour adjusted for the time zone in this case?
What Time zone are you using in config/application.rb? Rails adjust printed date to the Time Zone set in this file.

Weird created_at behavior

I've set config.time_zone = 'UTC' in environment.rb, and yet still I get some weird behavior with Rails' built-in datetime fields:
>> Time.now
=> Sun Jun 21 17:05:59 -0700 2009
>> Feedback.create(:body => "testing")
=> #<Feedback id: 23, body: "testing", email_address: nil, name: nil, created_at: "2009-06-22 00:06:09", updated_at: "2009-06-22 00:06:09">
>> Time.parse(Feedback.last.created_at.to_s)
=> Mon Jun 22 00:06:09 UTC 2009
Any thoughts?
It looks like it's properly setting the timezone in the ActiveRecord object, so I don't think you need to worry too much. If you want to force your timestamp from Rails to use UTC, you can use Time.utc.
Time.now.utc
=> Mon Jun 22 00:54:21 UTC 2009

Resources