I'm trying to figure out the right approach for calculating the remainder days of a user subscription.
For example; user signs up December 25, 2013 for a month and unsubscribed December 29, 2013. Even though he unsubscribed 4 days after subscribing, his subscription should run on PLAN A for the next 27 days (31 day based month).
I'm thinking I would be using the created_at and updated_at of subscription model. So far I got to
**MODEL: subscription.rb
class Subscription < ActiveRecord::Base
before_save :set_expiry_date
def set_expiry_date
#remaining_days = Subscription.calculate(:created_at - Date.today)
Subscription.expiry = '#remaining_days'
Subscription.save
end
end
Something like that but I'm missing something here and this might be an ugly approach. I'm guessing this gives anyone who can help small understanding what I'm after.
I would then run a rake as cron each day at 23:59 that removes 1 (day) from Subscription.expiry number in there and when it finds a 0, it would update something. That's another question though but I placed the stuff about the rake cron so you see where I'm heading with this.
If this were my project, I would take a different approach. I would set subscription expiry to the actual expiry date rather than a decrementing integer. That makes less work for the cron and also feels like good date practice . . . you get the benefit of persisting expiration dates after the expiration, which might be handy for later data analysis. What if there is a bug in your program or your cron fails to run? By persisting the date, you can do some detailed homework or rerun your crons against a specific cohort.
If I needed to know how many days were remaining on the subscription for UI or for API responses, I could call a method on the subscription class that looks something like the remaining_days method below:
def set_expiry_date
#automatically adds a month
#http://ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#method-i-next_month
Subscription.expiry_date = Subscription.created_at.next_month
Subscription.save
end
def remaining_days
expired? ? 0 : (Date.today - Subscription.expiry_date).to_i
end
def expired?
(Date.today - Subscription.expiry_date).to_i <= 0
end
def expired_today?
Date.today == Subscription.expiry_date
end
Then, my daily cron that does something on expiration (send email beseeching the customer to come back?) would just look for subscriptions where expired_today? == true. My cron would then also ignore subscriptions that expired before today or are yet to expire.
Related
I have this logic into my head but find it difficult to put into code; I can but not sure when to perform this action with Sidekiq
Compare last month's expenses with this month's expenses (end of this calendar month)
Worker Class:
def perform(*args) # params left out for this example
[...]
last_month = user.expenses.where(date: 1.month.ago) # ie Jan
this_month = user.expenses.where(date: Date.today.at_beginning_of_month..Date.today.end_of_month) # ie Feb (end of Feb)
# Then my if else here
end
Controller:
CompareExpenseWorker.perform_in(...)
I am stuck there. When should this perform? In 1 or 2 months?
I feel my mind is playing games on me. If I perform in 1 month, I will only get last month but not the current month ( full calendar month). If performed in 2 months, I feel that's not correct. Is it?
In the first place, the calculation for the last_month should be:
# last_month = user.expenses.where(date: 1.month.ago) # incorrect
last_month = user.expenses.where(
date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month)
I believe, the whole logic should be changed, though. If you want to compare two full months, it should be done as:
month_2 = user.expenses.where( # e.g. Feb
date: 2.month.ago.beginning_of_month..2.month.ago.end_of_month)
month_1 = user.expenses.where( # e.g. Mar
date: 1.month.ago.beginning_of_month..1.month.ago.end_of_month)
# run at any day in April, to compare two full months
# user_id is the id of the user to build reports for
CompareExpenseWorker.perform(user_id)
Furthermore, I don’t think the call to CompareExpenseWorker should be put into controller, use cronjob for that, scheduled at 1st of every month.
In my view, I will be ordering a list of items by their start_time. There are instances, however, where there might be a start_time of 1:00 AM (the following day) that should show up as after a start_time of 11:00 PM (previous day). Since the Time datatype stores a default date of 2001-01-01, it considers an entry of 1:00 AM to be on 2001-01-01, not 2001-01-02.
Thus, my question is, is there a way to change the default date in the Time datatype?
I suppose an obvious solution would be to instead store the start and end times in a DateTime datatype and enter a corresponding date. For this application, however, it is customary to refer to a start time of 1:00 AM as "belonging to" the previous day and would thus be confusing to enter the following day's date. (E.g. When attending your favorite band's concert on a Friday night, their set might start at 12:30 am Saturday morning, but you would still consider the concert to be on a Friday night). Thank you for your help.
Not a direct answer to your question, but a possible solution to your problem: you could create a custom sort that sorts times < 1 AM last.
sorted_concerts = Concert.order('CASE WHEN start_time <= "2001-01-01 01:00:00" THEN 2 ELSE 1 END, start_time')
This way you can leave the db columns as is but get your expected order for concerts. Excepting for this 1 AM inversion, the times will sort normally in ascending order.
After much more research, it doesn't seem like the default date of "2001-01-01" that Ruby applies to a time stored in a Time datatype can be adjusted. I guess this answers my question - but - to solve the problem, I changed the db columns to datetime datatype and logged a default date, that would adjust if the time entry was after midnight. Controller action below:
def create
start_time = DateTime.parse("#{run_of_show_item_params[:date]} #{run_of_show_item_params[:start_time]}")
end_time = DateTime.parse("#{run_of_show_item_params[:date]} #{run_of_show_item_params[:end_time]}")
#run_of_show_item = RunOfShowItem.new(run_of_show_item_params)
#run_of_show_item.start_time = start_time
#run_of_show_item.end_time = end_time
#run_of_show_item.start_time+=1.days if run_of_show_item_params[:start_time] < "07:00"
#run_of_show_item.end_time+=1.days if run_of_show_item_params[:start_time] < "07:00"
if #run_of_show_item.save
flash[:success] = "Performance added!"
redirect_to :back
else
render 'new'
end
end
I am writing a ruby program that will ask a user a to type a message, then a year, month, day and time and then email a message when that time comes. I store that data into a database table. The program should loop until that day comes and then carry out the sending.
Without using a database, I have written the following:
def send_Message(m_to, m_from, m_body, month, day, year, hour, min)
x = 0
t = Time.now
if (t.day == day
&& t.month == month
&& t.year == year
&& t.strftime("%I") == hour
&& t.strftime("%M") == min )
x = x +5
message = #client.account.sms.messages.create(:body => m_body,
:to => m_to,
:from => m_from)
puts message.sid
else
sleep_time = Time.new(year,month,day)
total_sleep = sleep_time - t
sleep(total_sleep)
message = #client.account.sms.messages.create(:body => m_body,
:to => m_to,
:from => m_from)
puts message.sid
end
end
but it only really seems to work with the month, day, year thing, but not time. There are two questions I am seeking an answer to: how can I carry out looping through a database, seeking chronologically messages to send, and with my example code above, how can I use the hour and minute of the day along with the day month and year to send the message.
P.S: I am designing this app on over to rails, where a user/app sends an API request and then the process I mentioned will be carried out.
You have a few approaches:
Look into the gem whenever
gem install whenever
https://github.com/javan/whenever
It doesn't do exactly what you asked but it is much better than running ruby for 6 month's waiting for an email to be sent.
An even better approach is resque https://github.com/defunkt/resque
Then add https://github.com/bvandenbos/resque-scheduler and your solution is very nice without the drag of a million ruby instances waiting to send an email.
Good Luck...
BTW: I made a service to do exactly what you are asking. I have specs and the code. If you want I can get it to you.
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
FYI, There is some overlap in the initial description of this question with a question I asked yesterday, but the question is different.
My app has users who have seasonal products. When a user selects a product, we allow him to also select the product's season. We accomplish this by letting him select a start date and an end date for each product.
We're using date_select to generate two sets of drop-downs: one for the start date and one for the end date.
Including years doesn't make sense for our model. So we're using the option: discard_year => true
When you use discard_year => true, Rails sets a year in the database, it just doesn't appear in the views. Rails sets all the years to either 0001 or 0002 in our app. Yes, we could make it 2009 and 2010 or any other pair. But the point is that we want the months and days to function independent of a particular year. If we used 2009 and 2010, then those dates would be wrong next year because we don't expect these records to be updated every year.
My problem is that we need to dynamically evaluate the availability of products based on their relationship to the current month. For example, assume it's March 15. Regardless of the year, I need a method that can tell me that a product available from October to January is not available right now.
If we were using actual years, this would be pretty easy.
For example, in the products model, I can do this:
def is_available?
(season_start.past? && season_end.future?)
end
I can also evaluate a start_date and an end_date against current_date
However, in setup I've described above where we have arbitrary years that only make sense relative to each other, these methods don't work. For example, is_available? would return false for all my products because their end date is in the year 0001 or 0002.
What I need is a method just like the ones I used as examples above, except that they evaluate against current_month instead of current_date, and past? and future months instead of years.
I have no idea how to do this or whether Rails has any built in functionality that could help. I've gone through all the date and time methods/helpers in the API docs, but I'm not seeing anything equivalent to what I'm describing.
Thanks.
Set the year of the current date to 1000 and perform a range compare with the start and end dates.
def is_available?
t = Date.today
d = Date.new(1000, t.month, t.day)
(season_start..season_end).include?(d) or
(season_start..season_end).include?(d+1.year)
end
Second comparison above is to address the following scenario:
Lets say today Jan 15 and the season is from Oct - Feb. In our logic, we set the date to Jan 15 1000. This date will not be within Oct 1 1000 - Mar 1 1001. Hence we do the second comparison where we advance the date by a year to Jan 15 1001, which is within the range.
You might want to consider not using Date objects. What if season_start_month and season_end_month were each an integer 1-12, set with your dropdown? Then when doing your is_available? comparison, you could dynamically create the full date for season_start, doing some math for transitions over December to January. This could use some refactoring, and isn't tested, but should do the trick. Assumes that season_start_month and season_end_month are integers stored in this model:
class Product < ActiveRecord::Base
def is_available?
now = Time.now
season_start < now && now < season_end
end
def season_start
now = Time.now
current_month = now.month
season_start_year = season_start_month < current_month ? now.year : now.year - 1
Time.local(season_start_year, season_start_month)
end
def season_end
now = Time.now
current_month = now.month
season_end_year = season_end_month > current_month ? now.year : now.year + 1
Time.local(season_end_year, season_end_month)
end
end