Reputation: 585
One shop has_many
products and a product belongs_to
a shop.
The shop has opening_hours
and closing_hours
columns. Each column stores a hash which looks like the following:
opening_hours = {'Monday' => '12:00', 'Tuesday' => '13:00'}
and so on
I am working with current date and time, and I would like to retrieve all products which stores have opening_hours
smaller than current time which is smaller than closing_hours
.
Basically all the products that are available when the shop is open.
I did something like that in my controller:
day_today = Date.today.strftime('%A')
time_now = Time.new.strftime('%H:%M')
@products = Product.joins(shop: [{opening_hours[day_today] < time_now,
{closing_hours[day_today] > time_now }])
Edit
This is the code that stores the opening times in the DB
hours_table = shop.search('table')
opening_hours = {}
closing_hours = {}
hours_table.first.search('tr').each do |tr|
cells = tr.search('td')
day = cells.first.text.strip
hours = cells.last.text.strip.tr('-', '').split
opening_hours[day] = hours[0].to_time.strftime('%H:%M')
closing_hours[day] = hours[1].to_time.strftime('%H:%M')
end
shop = Shop.new(
name: shop_name,
opening_hours: opening_hours,
closing_hours: closing_hours
)
shop.save
new_product = Product.new(
name: foo,
description: foo,
price: foo,
company: 'foo',
)
new_product.shop = shop
new_product.save
This is the migration to add opening hours to Shop. The same is done for closing hours.
class AddOpeningHoursToShop < ActiveRecord::Migration[5.1]
def change
add_column :shops, :opening_hours, :json, default: {}
end
end
This is the migration to add the shop ID to products
class AddShopToProducts < ActiveRecord::Migration[5.1]
def change
add_reference :products, :shop, foreign_key: true
end
end
Any guess?
Upvotes: 0
Views: 858
Reputation: 33491
You can use the ->>
operator to access a specific JSON field as text:
SELECT opening_hours->>'Monday' AS monday_opening_hour FROM shops;
Same for closing_hours
, it just changes the name of the column and field.
So the AR representation in your case would be like:
Product
.joins(:shop)
.where(
"shops.opening_hours->>:day <= :now AND shops.closing_hours->>:day >= :now",
day: day_today,
now: time_now
)
You can easily bind the key name and the time you're going to ask for, here as day
and now
.
Notice, it's not needed to explicitly prepend the table name, but I'm doing so just for the sake of clarity.
This can also be done with the BETWEEN operator:
Product
.joins(:shop)
.where(
":now BETWEEN shops.opening_hours->>:day AND shops.closing_hours->>:day",
day: day_today,
now: time_now
)
You can use the one you think is more clear.
In any case, you can get rid of storing the time_now
variable, by using the NOW
function of PostgreSQL. It'll handle the time format difference if you cast it to text (NOW()::text
):
Product
.joins(:shop)
.where(
"NOW()::text BETWEEN shops.opening_hours->>:day AND shops.closing_hours->>:day",
day: day_today
)
Similarly, the day can also be obtained from NOW
. You can use to_char(NOW(), 'Day')
for that
SELECT to_char(date, 'Day') AS day
FROM generate_series (NOW() - '6 days'::interval, NOW(), '1 day') date;
day
-----------
Thursday
Friday
Saturday
Sunday
Monday
Tuesday
Wednesday
So:
Product
.joins(:shop)
.where("NOW()::text BETWEEN shops.opening_hours->>(to_char(NOW(), 'Day'))
AND shops.closing_hours->>(to_char(NOW(), 'Day'))")
Upvotes: 1