How to reset timestamp to "nil" at end of day? - ruby-on-rails

At the end of each day I intended for this code to reset the timestamp completed_at back to nil, but it's not working.
def completed=(boolean)
self.completed_at = boolean ? Time.current : nil
end
def completed
completed_at && completed_at >= Time.current.beginning_of_day
end
What's the point?
The user checks off in the view that he did his habit:
<%= link_to ''.html_safe, mark_completed_path(habit), remote: true, method: 'put', class: 'update_habit' %>
The habit then fadeOut's but the idea is that the habit should return the following day so that everyday the user must check off the habit again.
def home
#habits = current_user.habits.committed_for_today.incomplete.order(:order)
end

You dont need to set the time to nil at the end of the day, you need to change your scope for incomplete so that it will consider all habits to be incomplete where completed_at is not its current date
if you want all habits, where completed_at is less than the current date. do this
scope: incompleted, -> {where("completed_at is null OR completed_at < ?", Date.current)}
This way you dont need any rake task and your logic will be used to consider habits incomplete per day.

Precedence issue, try:
def completed=(boolean)
self.completed_at = (boolean ? Time.current : nil)
end

Related

Rails Rspec test fails when it works in app

I have the following code and test that I can't seem to make pass. The code should be auto locking all bookings that have been completed over 24 hours ago.
When I put a pry into the test and run the first line of Booking.auto_lock_guests nothing happens. When I type booking_7 and after type Booking.auto_lock_guests then it changes locked to true. Is this something to do with the way let is set up that it is not showing up in Booking.all? Or is it the way I have written the test?
Any help would be greatly appreciated.
def self.auto_lock_guests
bookings = Booking.where(guests_completed: true, locked: false)
bookings.each do |booking|
next unless booking.guests_completed_at <= 1.day.ago
booking.locked = true
booking.save
end
end
context 'auto_lock_guests' do
let(:booking_6) { FactoryGirl.create(:booking, date: Date.today - 5.day, guests_completed: true, guests_completed_at: DateTime.now, locked: false )}
let(:booking_7) { FactoryGirl.create(:booking, date: Date.today - 5.day, guests_completed: true, guests_completed_at: DateTime.now - 3.day, locked: false )}
before do
Booking.auto_lock_guests
end
it 'should only lock bookings with a guests_completed date older than a day ago' do
expect(booking_7.locked).to eq(true)
expect(booking_6.locked).to eq(false)
end
end
let is lazily evaluated. When the before block is executed there are no records, because the let blocks haven't yet been called.
Either change let to let! to execute the block immediately or call booking_6 and booking_7 right before Booking.auto_lock_guests
EDIT:
Also you don't check wether the booking.save succeeded. If booking.save failed - you would never know. :)
The next unless booking.guests_completed_at <= 1.day.ago could probably be rewritten as a query: where(Booking.arel_table[:guests_completed_at].gt(1.day.ago))
You don't need to iterate through the records in the first place. In fact it will cause problems as your app scales since pulling all those records into memory will exhaust the servers (or dynos) memory.
You can select the records from the database and update them in a single query:
class Booking
def self.auto_lock_guests!
bookings = Booking.where(guests_completed: true, locked: false)
.where('guests_completed_at <= ?', 1.day.ago)
bookings.update_all(locked: true)
end
end
The difference in execution time between many individual UPDATE queries and an updating many rows at once can be massive.
To test it you can create multiple records and use change expectations:
# use describe and not context for methods.
describe ".auto_lock_guests" do
# let! is not lazy loading
let!(:old_booking) { FactoryGirl.create(:booking, date: 7.days.ago, guests_completed: true, guests_completed_at: 3.days.ago, locked: false )}
let!(:new_booking) { FactoryGirl.create(:booking, date: Date.today, guests_completed: true, guests_completed_at: DateTime.now, locked: false )}
it 'locks a booking with a guests_completed date older than a day ago' do
expect do
Bookings.auto_lock_guests! && old_booking.reload
end.to change { old_booking.locked }.from(false).to(true)
end
it 'does not lock a when guests_completed date is less than a day ago' do
expect do
Bookings.auto_lock_guests! && new_booking.reload
end.to_not change { new_booking.locked }.from(false).to(true)
end
end
Using change is a very good idea when testing methods that change the database as they verify both the initial state and the result.
I ended up having to add this into the before action after calling Booking.auto_lock_guests and it worked.
before do
Booking.auto_lock_guests
booking_7.reload
booking_6.reload
end

rake task to expire customers points balance

i am trying to work out how to write a rake tasks that will run daily and find where the days remaining is 0 to update the column amount to zero.
I have the following methods defined in my model, though they don't exactly appear to be working as I am getting the following error in the view
undefined method `-#' for Mon, 27 Jun 2016:Date
def remaining_days
expired? ? 0 : (self.expire_at - Date.today).to_i
end
def expired?
(self.expire_at - Date.today).to_i <= 0
end
def expire_credits
if expired?
self.update(:expire_at => Date.today + 6.months, :amount => 0)
end
end
with the rake tasks i have never written of these and i thought i would be able to call a method of StoreCredit that would expire the points if certain conditions are met but i am not sure how this all works
task :expire_credits => :environment do
puts 'Expiring unused credits...'
StoreCredit.expire_credits
puts "done."
end
# model/store_credit.rb
# get all store_credits that are expired on given date, default to today
scope :expire_on, -> (date = Date.current) { where("expire_at <= ?", date.beginning_of_day) }
class << self
def expire_credits!(date = Date.current)
# find all the expired credits on particular date, and update all together
self.expire_on(date).update_all(amount: 0)
end
end
Since it's a rake task, I think it's more efficient to update all expired ones together
#rake file
result = StoreCredit.expire_credits!
puts "#{result} records updated"
Retrieve Record Count Update
class << self
def expire_credits!(date = Date.current)
# find all the expired credits on particular date, and update all together
records = self.expire_on(date)
records.update_all(amount: 0)
records.length
end
end
You call class method but define instance method. You will need to define class method:
def self.expire_credits

Fetching Data based date and time

I am trying to find results from today onwards but also want to include the yesterdays plans if the time is between 12:00am-5:00am
Right now i have the following
def self.current
where(
"plan_date >= :today",
today: Date.current,
)
end
Is there a way i can know the time of the day based on the users timezone which am setting as bellow in the app controller and make sure that if its before 6:am the next day i want to include the previous days results as well.
def set_time_zone(&block)
if current_user
Time.use_zone(current_user.time_zone_name, &block)
else
yield
end
end
Try this:
def self.current
where(
"plan_date >= :today",
today: (Time.zone.now.in_time_zone(get_user_time_zone) - 6.hours).beginning_of_day,
)
end
...where get_user_time_zone returns the time zone for the user (E.G.: America/New_York). I'm using - 6.hours because you wanted it to be "before 6am" local time.

updating a model attribute that will only change once

I have a subject model with attributes including a start_date and end_date - as well as a completed boolean attribute.
In subject.rb, I have a method to find how many weeks are remaining for a given subject:
def weeks_left
time = (self.end_date.to_time - Date.today.to_time).round/1.week
if time < 0
"completed"
elsif time < 1
"less than 1 week"
elsif time == 1
"1 week"
else
"#{time} weeks"
end
end
I want to tick the completed attribute if self.weeks_left == "completed" and the best way to do that seems like a call back, but I'm a bit unsure about using after_find - in general it seems like it would be too many queries, and indeed too big of a pain (especially after reading this) - but in this case, once a subject is complete, it's not going to change, so it seems useless to check it's status more than once - what's the best way to handle this?
Why dont you make a scope for this?
scope :completed, ->{where("end_date <= ?", Time.now)}
and a method
def completed?
self.weeks_left == "completed"
end
Looks like you need ActiveRecord::Callbacks. You can see more information here or on rails guide
before_save :update_completed
def update_completed
if (end_date_changed?)
time = (self.end_date.to_time - Date.today.to_time).round/1.week
self.complete = time < 0
end
end
This way you update the complete flag whenever end_date changes and it would always be in sync.
However because this is a calculated value you could also not store it as an attribute and simply define a method to get it
def complete
time = (self.end_date.to_time - Date.today.to_time).round/1.week
return time < 0
end

Time.zone.now locked in production

In prodction, method Time.zone.now always return the same value while Time.now returns
the correct system time.
Here one example:
banner.rb
scope :active, where("'#{Time.zone.now}' between start_date and end_date")
log of this scope:
SELECT `banners`.* FROM `banners` WHERE ('2013-03-06 08:06:46 -0300' between start_date and end_date) AND ((`banners`.`store_id` = 1 AND `banners`.`spot_id` = 3 AND `banners`.`at_home` = 1))
Time.zone.now is always 2013-03-06 08:06:46 -0300
Anyone know why?
This is because the scope is evaluated once, when the class is loaded.
You should define it like this:
def self.active
where("'#{Time.zone.now}' between start_date and end_date")
end
Every time this method is called, it will be re-evaluated and so the time will change.

Resources