I have a setup where I would like to send automatic reminders for users if they haven't made a monthly rental payment. The landlord has the opinion to define the day of the month on which the tenant should make the payment(due_date). What I would like to achieve is that if the payment hasn't been made in 2 days since the due_date the tenant would be sent an email. Currently, I have the following task for this.
namespace :rent_due_reminder do
desc "Sends monthly reminders to tenants who haven't paid their rent."
task :send => :environment do
Offer.all.each do |offer|
if offer.status == "accepted" && offer.due_date.present?
landlord = User.find_by(id: offer.landlord_id)
tenant = User.find_by(id: offer.user_id)
stripe_account = Stripe::Account.retrieve(landlord.merchant_id)
payments = Stripe::Charge.list(
{
limit: 100,
expand: ['data.source_transfer.source_transaction.dispute', 'data.application_fee'],
source: {object: 'all'}
},
{ stripe_account: landlord.merchant_id }
)
due_date = "#{Date.today.year}-#{Date.today.month}-#{offer.due_date}".to_datetime
last_payment_date = DateTime.strptime(payments.data.last.created.to_s,'%s')
if (SOME CONDITION)
OfferMailer.with(offer: offer, tenant: tenant, landlord: landlord).rent_due.deliver
end
end
end
end
end
So what I'm doing is that I'm picking the last payment and getting the date of that and this is what I should use to condition the mailer. Initially, I thought that I could just look and see whether last_payment_date < due_date + 2, but this didn't seem to work. I might be getting confused about this since if I have multiple landlords and some of them have their due date for example 30th of every month and some of them have 1st of every month, should I still be running the task monthly or how should I set this up? Apologies for the reasonably bad explanation, I myself am getting very confused with this also.
There is a flaw in your logic. Even if a user made a payment on the due date, the last_payment date would still be less than due_date+2. So you need to add one more check that ensures that the payment was made for the current month.
e.g. If the due date for my rent is the 5th of every month you need to check the payment was made between 1st and 5th. Currently you are only checking if the last payment date was before 7th. That includes valid payments too.
Of course the above logic is flawed too because people could make advance payments and you will need to correct for that too. One way around it is to create a bill and ensure that all bills are paid rather than simply checking for last payments. That way you can calculate delinquencies for more than one month.
Related
I have a rufus scheduler in my Rails app that I'm using to record/insert data every 5 minutes from 9:30AM to 4:00PM Mon-Fri. The method containing this action is fired as soon as user is registered. BUT this logging job is intercepted and terminated once user logs out.
Could anyone help in figuring out how to persist the logging method even when the user logs out/session is destroyed?
I am calling the logging method in my Users controller create method and I've set up the logging method in the User model.
Any help would be greatly appreciated!
That sounds convoluted. Why don't you use a single scheduled job that is run every 5 minutes from 9:30AM to 4:00PM Mon to Fri for every User registered in the system.
do_log =
lambda do
User.where(status: 'active').each do |u|
# do the insertion for the user...
end
end
scheduler.cron('30,35,40,45,50,55 9 * * mon-fri America/New_York', &do_log)
scheduler.cron('*/5 10-15 * * mon-fri America/New_York', &do_log)
The User.where(status: 'active') is Sequel, it's probably different in Rails, but I am sure you can translate and adapt to your setting and needs.
Thus, as soon as a User is registered in your system, the logging will begin for him, even if he's logged out.
Yes, I know it's not a single job, it's two jobs, but that takes care of the "0930 to 1630" requirement.
Maybe that's not what you want, but you are not very clear in your question...
Update 2021-01-30 - 1416+0900
#hurricanenara commented:
I'm not certain where the do_log would be called to act as an all-encompassing job that will be applied to all "active" users during the scheduled times
The do_log will be called '30,35,40,45,50,55 9 * * mon-fri America/New_York' and '*/5 10-15 * * mon-fri America/New_York' which corresponds to your requirement:
every 5 minutes from 9:30AM to 4:00PM Mon-Fri
If it is easier to you, the do_log can be "forgotten" if writing
scheduler.cron('30,35,40,45,50,55 9 * * mon-fri America/New_York') do
User.where(status: 'active').each do |u|
# do the insertion for the user...
end
end
scheduler.cron('*/5 10-15 * * mon-fri America/New_York') do
User.where(status: 'active').each do |u|
# do the insertion for the user...
end
end
As you can see, the do_log lambda avoids a repetition (Don't Repeat Yourself, DRY).
This method assumes that when a "User is registered" in your system, they get added to the database in a user table fronted by a User model. The do_log simply iterates over all the user whose status is "active" (not "left", or "revoked", or "whatever"), and records information for that particular user.
Your problem description is vague. I'm guessing that you want to log for each user, because you want to log the information "even when the user logs out/session is destroyed".
I don't know what you mean by "persist the logging method". I'm not sure you mean "persist to disk", so my guess is that you mean "keep on running".
It is your responsibility as a developer to clearly state your problem. Most of the time that effort at clearly stating the problem will yield the answer to you without requiring another developer to listen. See also Rubber Ducking.
I've to make posts inactive after a given date and time by the user who creates these posts. I've added a boolean field "published" to make a post active or unactive.
If the post is active, every visitor will see the post. If it's inactive, the post will be seen in the user dashboard as inactive.
For you guys what is the best way to make a post inactive after the given datetime passed? That should be automatic but I don't think the suitable way is using Active Job and ;
Check all posts in the db if active,
If active, compare the given datetime and current datetime
If needed, make it inactive
I'm using heroku, so that'll make my worker busy all the time.
If you simply plan to fetch and display posts, rather than setting the boolean value, you could just add a scope to the model:
scope :published, -> { where('publish_at < ?', Time.now) }
And use like this Post.published
Let me know if this helps.
Update 1:
If you need to determine if a post is active or not, add a method to the model like this,
def active?
self.publish_at < Time.now
end
You can do some things:
Option 1:
Based in the #jawad-khawaja answer, I think a better approach is to use expired_at instead of published_at because for me, a publish date is a date when a post is available for people. This way the user can define an expiration date for his own post (not the same publish date).
Here some example how to you can get active/inactive posts based on its expiration date
# active posts
scope :published, -> { where('expired_at > ?', Time.now) }
# inactive posts
scope :unpublished, -> { where('expired_at < ?', Time.now) }
Option 2:
If you have an attribute published as boolean and you really need mark it automatically, the option is to use whenever gem
CONS for this option:
You will have two attributes representing the same thing: published as boolean and expiration_datetime as datetime
If the minimum unit for an expiration date is a minute, you need to check every minute if every not-expired post enter to the expired state. And you will have probably an overload for your server.
Conclusion:
My recommended way is to choose the option 1.
Firstly, this question may stray into opinion but I think it's a valuable question to ask. I will give a very specific example for my application which handles absence management and tracking.
An Account has many Users and a User has many Absences. The Account can create PublicHolidays which should be ignored when calculating the number of days that an Absence uses.
Example: If a person takes a week off, the days used will be 5. If one of those days is a PublicHoliday, the days used would be 4.
I want to implement a method such that when a PublicHoliday is created, the days used for any Absences created prior to the date of creation and which cross the date of the PublicHoliday are recalculated.
My current RSpec test looks like this:
it 'triggers a recalculation of absence days on create for absences created before the date of creation of the public holiday' do
robin = FactoryGirl.create(:robin)
absence = FactoryGirl.create(:basic_absence, user: robin)
expect(absence.days_used).to eq(1)
ph = FactoryGirl.create(:public_holiday, country: "England", account: robin.account)
expect(absence.reload.days_used).to eq(0)
end
In this test, ph is the same date as the absence so I expect it to calculate one day to start with and then I intend to use an after create callback to recalculate the days used.
Is this the right way to do this test? Is there a more efficient way without creating a number of associated objects?
Firstly - it's good practice to use lets instead of local variables, and secondly - split your tests so each test tests just one thing. Thirdly: anything that sets up a context for tests should be put into a context-block (even if there's only one test in that context)
eg, here's a re-writing of your spec the standard way:
let(:robin) { FactoryGirl.create(:robin) }
let(:absence) { FactoryGirl.create(:basic_absence, user: robin) }
context "with no public holidays" do
it 'counts the absence day to be a day used' do
expect(absence.days_used).to eq(1)
end
end
context "with a public holiday for the absence" do
before do
FactoryGirl.create(:public_holiday, country: "England", account: robin.account)
end
it 'does not consider the absence day to be a day used' do
expect(absence.days_used).to eq(0)
end
end
I am currently building a site that runs an autonomous competition every week. The logic I have checks if the previous week has a winner assigned, and if it does not, it rolls through, finds a winner and assigns a trophy.
The logic all works, but a lot of it is run in the application controller, and in my core I feel this is not right.
For example, if first place has more votes than second place, and second place has more votes than third place, it would need to create a first second and third place trophy and reward it to the correct users.
if first_place > second_place && second_place > third_place
#week_previous.winner_id = week_entries[0].id
#week_previous.save
first_trophy = week_entries[0].user.trophies.new
first_trophy.week_id = #week_previous.id
first_trophy.user_id = week_entries[0].user_id
first_trophy.position = "first"
first_trophy.country = week_entries[0].user.country
first_trophy.pro = false
first_trophy.save
if second_place >= 1
second_trophy = week_entries[1].user.trophies.new
second_trophy.week_id = #week_previous.id
second_trophy.user_id = week_entries[1].user_id
second_trophy.position = "second"
second_trophy.country = week_entries[1].user.country
second_trophy.pro = false
second_trophy.save
end
if third_place >= 1
third_trophy = week_entries[2].user.trophies.new
third_trophy.week_id = #week_previous.id
third_trophy.user_id = week_entries[2].user_id
third_trophy.position = "third"
third_trophy.country = week_entries[2].user.country
third_trophy.pro = false
third_trophy.save
end
end
This is building the Trophies directly into the controller, and I've often heard the 'fat model skinny controller' argument, and I feel the way I am running this goes totally against that!
How would I move the trophy creations into the model? I am sure I could use something like after_save in the weeks model, but I am not entirely sure how to keep the logic working. I've had a few attempts, but I am often getting the error undefined method to_model.
I know I could just plough on and get it working, but I just feel like it's not the 'Rails Way' of doing things, so I'd like to work it out in its early stages.
Any help is greatly appreciated.
Thanks!
Edit based on comments:
Thanks for taking the time to look at this. In a nut shell, what I am trying to achieve is a system where a competition runs from Monday to Sunday. The 'Active Week' is scoped to the Week where the Date.today falls between the :start_date and :end_date.
The following Monday starts a new week, this moves what was the active week to the previous week, and it then allocates trophies to the top 3 entries from the previous week. It allocates the trophies by checking if the previous week has a winner_id. If it does, it doesn't run any of the logic, but once the scope moves onto a new week, the previous weeks :winner_id is now nil, so the logic runs the first time somebody comes to the site in the new week.
To dumb it down:
Week is a resource, which has_many Entries.
An Entry belongs_to a User, belongs_to a Week, and has_many Votes.
A Vote belongs_to an Entry
A User has_many Entries and has_many Trophies
A Trophy belongs to a User and belongs_to a Week
So, users Vote on an Entry on the current active Week. Once the week is outside of the active scope, it creates Trophies for the Users that placed in the top 3 positions of the week.
It is hard to give with a good advise without knowing the context. But what catches my eye is that there is a lot of repetion in that code and that you update user trophies in that repetitions. Therefore I would at least move that logic into the user as a first step:
# in user.rb
def record_trophy_win(prev_week, entry, position)
trophies.create(
week_id: prev_week.id,
user_id: entry.user_id,
position: position,
country: entry.user.county,
pro: false
)
end
That allows to change the partial in the controller to this:
if first_place > second_place && second_place > third_place
#week_previous.update_attribute(:winner_id, week_entries[0].id)
first_trophy = week_entries[0].user.record_trophy_win(
#week_previous, week_entries[0], 'first'
)
second_trophy = week_entries[1].user.record_trophy_win(
#week_previous, week_entries[1], 'second'
) if second_place >= 1
third_trophy = week_entries[2].user.record_trophy_win(
#week_previous, week_entries[2], 'third'
) if third_place >= 1
end
That logic might in a next step belong into a Trophy class. Depends on the context...
And I noticed that your week_entry has an user. And you add to that user a trophy that again has some user fields. Doesn't that result in a circular dependency? Or do you override an existing record with the exact same entries? That need some clearity.
One way to isolate business logic is the Service Object design pattern. Example:
class TrophyRewarder
def initialize(winners, last_week_winners)
#winners = winners
#last_week_winners = last_week_winners
end
def reward(options)
# All code to determine and store winners comes here
end
end
You can put this code in the folder app/services and call it in your controller like this:
trophy_rewarder = TrophyRewarder.new(users, Winners.from_last_week)
trophy_rewarder.reward(options)
The good thing is that you can call this service object from a controller but also from a background task. Another good thing is that the service object can use a lot of different data/AR models, but is is not tied to a model.
I hope this helps a little in showing how you can organize your code.
I'm aware of the customer.subscriptions.trial_will_end event. It fires 3 days before a trial ends.
I couldn't find an event that actually fires when the trial is over and the customer hasn't paid. This would be useful to do something simple like this to turn off features:
customer.update_attributes(active_account: false)
Without a webhook like that, I'm looking at scheduling some tasks to check unconfirmed customers periodically and turn off features accordingly. The webhook seems cleaner though and less prone to errors on my side. Is there an event/webhook in line with these goals? FYI, customers don't have to put in a card when they start the trial - so autobilling is not an option.
When the trial period ends, there will be a customer.subscription.updated event and an invoice.created event. An hour (or so) later, you'll then either see an invoice.payment_succeeded event or an invoice.payment_failed event. From those, you'll know whether the payment went through or not.
Cheers,
Larry
PS I work on Support at Stripe.
To add to Larry's answer and share how I got around the lack of a trial ended webhook, here's what I did.
In invoice.payment_failed webhook, I checked:
Is this the first invoice since the subscription start?
Does the customer have any cards saved?
If these checks fail, then I assume the trial has just ended with no billing details entered, and I cancel the subscription.
Example in Python:
# get account from my database
account = models.account.get_one({ 'stripe.id': invoice['customer'] })
# get stripe customer and subscription
customer = stripe.Customer.retrieve(account['stripe']['id'])
subscription = customer.subscriptions.retrieve(account['stripe']['subscription']['id'])
# perform checks
cards_count = customer['sources']['total_count']
now = datetime.fromtimestamp(int(invoice['date']))
trial_start = datetime.fromtimestamp(int(subscription['start']))
days_since = (now - trial_start).days
# cancel if 14 days since subscription start and no billing details added
if days_since == 14 and cards_count < 1:
subscription.delete()
Just add 3 days to the free trial period and use the customer.subscriptions.trial_will_end event and update the subscription with 'trial_end=now'
I think there is another way that can be handled easly. So invoice.payment_failed should be listened, in all invoice related events, inside event.data.object, there is subscription id or subscription object, you should get subscription id and retrieve subscription then you can get both product id and price id.
By price id or by product id you can know current subscription.