In my app a User has_many Gestures. What is a good way to calculate how many subsequent days the User has done Gestures?
Right now I'm doing it like below, and it works as I want it to. But clearly it doesn't scale.
class User < ActiveRecord::Base
# ...
def calculate_current_streak
return 0 unless yesterday = gestures.done_on_day(Date.today-1)
i = 0
while gesture = self.gestures.done_on_day(Date.today - (i+1).days).first
i += 1
end
i += 1 if gestures.done_on_day(Date.today).first
i
end
end
Thanks! Special points to the one who can work in a way to only track business days too :)
Think about it this way:
The length of the streak is equivalent to the number of (business) days passed since the last (business) day the user didn't use gestures at all, right?
So the solution boils down to calculating most recent day that the user didn't make a gesture.
In order to do this most easily, there's a trick that lots of DB admins use: they create DB table with all dates (say, all the dates from year 2000 to year 2100). The table needs to have only date field, but you can throw in a Boolean field to mark non-working days, such as weekends and holidays. Such table can be handy in lots of queries, and you only have to create and fill it once. (Here's a more detailed article on such tables, written for MS SQL, but insightful anyway.)
Having this table (let's call it dates), you can calculate your pre-streak date using something like (pseudo-SQL):
SELECT d.date
FROM dates d
LEFT JOIN gestures g ON d.date = g.date AND g.user_id = <put_user_id_here>
WHERE d.date < TODAY() AND g.date IS NULL AND d.work_day = TRUE
ORDER BY d.date DESC
LIMIT 1
Related
I am working on an hourly hotel booking application. There is a peak_seasons table which stores start_date and end_date. Peak_Seasons are pre-defined dates for current year in my application. Whenever a customer makes a booking for the selected hours then I have to check if any of those hours belongs to peak season, so that I can apply a different rate for those hours.
This operation is so frequent that I want to optimise it. Need ideas. My current pseudo code is this:
def calculate_price(customer_start_time, customer_end_time)
price = 0
(customer_start_time.to_i...customer_end_time.to_i).step(1.hour) do |hour|
//now check if this hour belongs to peak season over here
if peak_seasons?(hour)
price += price + peak_season_price
else
price += price + standard_price
end
return price
end
//peak_seasons? somewhere
def peak_seasons?(hour)
PeakSeason.where("start_date <= ? and end_date >= ?", hour.to_date, hour.to_date).any?
end
I guess this is not efficient code when hundreads of customers are checking the price for selected hours, then it will fetch data from DB for every hour selected. How to optimize it?
You could create a super efficient solution by caching all PeakSeason data and using an Interval tree (see also this answer) for calculation. But you say "I guess this is not efficient" - honestly, I advice against this kind of optimization unless you really know there is a performance problem.
You could try to select all PeakSeason records for a given interval at once.
def all_peak_seasons(customer_start_time, customer_end_time)
PeakSeason.where(":start_time BETWEEN start_date AND end_date OR "\
":end_time BETWEEN start_date AND end_date OR "\
"start_date BETWEEN :start_time AND :end_time",
{start_time: customer_start_time, end_time: customer_end_time})
end
I have the an Order model in the following
If today is 5/3
and I want to sum the previous 3 months data of order, how to do it ?
I mean I want to show the 2/1 ~ 4/30 excluding the orders in May.
If today is 2014/4/20, and I want to show the sum of previous 3 weeks data. 2014/2/1~2/15
How to do it in Rubic way ?
You want something along the lines:
date = DateTime.now.utc
Order.where('created_at >= ? and created_at <= ?', date.beginning_of_month, date.utc.end_of_month).sum('price')
Where price is the column you want to sum.
You can reuse the logic of #Santosh in order to get the dates you want =)
start_date = 3.months.ago.beginning_of_month
end_date = 1.month.ago.end_of_month
You can write your query based on these dates. Same logic can be applied for weeks also.
I need to every query involving my Issue model appends another column to its rows.
In my case this column is a boolean if the issue's created_at date from now passed a total of 12 hours (business hours: 8:00 am - 18:00 pm, excluding saturdays and mondays).
SELECT *, hours > 12 as alert FROM
(
SELECT *,
(SELECT count(*) - 1 AS work_hours
FROM generate_series ( created_at
, now()
, interval '1h') h
WHERE EXTRACT(ISODOW FROM h) < 6
AND h::time >= '08:00'::time
AND h::time < '18:00'::time) as hours
FROM issues
) as issues_with_alert
This will return all issues columns such as id, title, description..., plus the calculated one: alert
I thought making a sql view issues_view and change table name on the model to the view ones.
self.table_name = "issues_view"
Then the view should care about all the queries from it inheriting the alert column.
But it seems rails is not fully prepared for that and monkey patches should be made:
http://mentalized.net/journal/2006/03/27/using_sql_server_views_as_rails_tables/
and other problems I found.
Other one is to implements the method on the Issue model:
def self.default_scope
end
But I don't know how to fit the SQL inside of it.
What is the right way to achieve this with rails? I would like to avoid wrong patterns and low readability.
Thanks!
I believe you can solve this by adding a method to your model that will perform the desired calculation and return the boolean value.
I am creating a ROR app for an e-commerce site which handles the management of renting items for a period of time. The items will be physically delivered and picked-up. Item are always rented for 30 days.
So the problem I am facing, is I need to somehow get which days an item can be rented and is available for at least 30 days from that point. (for example, a customer couldn't rent an item today if it is reserved to be rented 10 days from now)
In my database I have a rentals table that stores the pickup and delivery date.
I will be using a jQuery datepicker, and just need to load available dates 1 month at a time (I can redo the query each time the next month button is pressed to hide unavailable dates)
What would be the best approach to performing this type of query and getting all the days in a month in which an item is available for 30 days? I could always iterate through every single day in the month and check if there are any other records within 30 days, but that seems like a surplus of queries.
Any help would be greatly appreciated.
Thanks,
Tyler
How do you know the day an item is reserved to be picked up? Make a query based on that. Perhaps your Rental model has a reserved_on attribute?
class Rental < ActiveRecord::Base
scope :available, where('rented = ? AND reserved_on > ?', false, 30.days.from_now)
end
EDIT in response to comments
If are looking at a single object you could create methods on it something like this:
def last_available_day
delivery_date && delivery_date - rental_period # 30 days
end
def is_available_on?(date)
return true unless last_available_day
date <= last_available_day
end
I need to grab the records for same day of the week for the preceeding X days of the week. There must be a better way to do it than this:
Transaction.find_by_sql "select * from transactions where EXTRACT(DOW from date) = 1 and organisation_id = 4 order by date desc limit 7"
It gets me what I need but is Postgres specific and not very "Rails-y". Date is a timestamp.
Anyone got suggestions?
How many days do you want to go back?
I have written a gem called by_star that has a dynamic finder suited for finding up to a certain number of days in the past. If the number of days was always a number you could use this finder:
Transaction.as_of_3_days_ago
If it was dynamic then I would recommend using something such as future or between, depending on if you have transactions in the future (i.e. time travel):
Transaction.future(params[:start_date].to_time)
Transaction.between(params[:start_date].to_time, Time.now)
AFAIK Rails has no any methods to do this by other way. So best, and faster, solution - build DOW index on date column and use your query.