Rails: set model attribute using a time reference - ruby-on-rails

1. The environment
I have an Auction model with these attributes:
status:string
close_date:datetime
Status of my auctions must be, automatically, set to 'ended' when Time.now
equals auction.close_date.
To do so, I think I need a way to check for this equality every second.
This is my method:
def set_ended
auctions = Auction.all.active # named_scope to get only 'active' auctions
auctions.each do |auction|
if auction.close_date == Time.now
auction.status = 'ended'
end
end
2. The question:
How can I set my application to run this method every second?

Take a look at Whenever, that's what you need.

Related

What is the best way to reuse a scope in Rails?

I'm confused to reuse or writing a new scope.
for example,
one of my methods will return future subscription or current subscription or sidekiq created subscriptions.
as scopes will look like:
scope :current_subscription, lambda {
where('(? between from_date and to_date) and (? between from_time and to_time)', Time.now, Time.now)
}
scope :sidekiq_created_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is not null")
}
scope :future_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is null")
}
so these were used for separate purposes in different methods, so for me what I tried is to check whether a particular account record will come under which of three subscriptions.
so I tried like:
def find_account_status
accounts = User.accounts
name = 'future' if accounts.future_subscription.where(id: #account.id).any?
name = 'ongoing' if accounts.current_subscription.where(id: #account.id).any?
name = 'sidekiq' if accounts.sidekiq_enqued_subscription.where(id: #account.id).any?
return name
end
so here what my doubt is, whether using like this is a good way, as here we will be fetching the records based on the particular subscriptions and then we are checking whether ours is there or not.
can anyone suggest any better way to achieve this?
Firstly, you are over using the scopes here.
The method #find_account_status will execute around 4 Queries as below:
Q1 => accounts = User.accounts
Q2 => accounts.future_subscription
Q3 => accounts.current_subscription
Q4 => accounts.sidekiq_enqued_subscription
Your functionality can be achived by simply using the #account object which is already present in memory as below:
Add below instance methods in the model:
def current_subscription?
# Here I think just from_time and to_time will do the work
# but I've added from_date and to_date as well based on the logic in the question
Time.now.between?(from_date, to_date) && Time.now.between?(from_time, to_time)
end
def future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].blank?
end
def sidekiq_future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].present?
end
#find_account_status can be refactored as below:
def find_account_status
if #account.current_subscription?
'ongoing'
elsif #account.future_subscription?
'future'
elsif #account.sidekiq_future_subscription?
'sidekiq'
end
end
Additionally, as far as I've understood the code, I think you should also handle a case wherein the from_date and to_date are past dates because if that is not handled, the status can be set based on the field meta["special_sub_enqueued_at"] which can provide incorrect status.
e.g. Let's say that the from_date in the account is set as 31st Dec 2021 and meta["special_sub_enqueued_at"] is false or nil.
In this case, #current_subscription? will return false but #future_subscription? will return true which is incorrect, and hence the case for past dates should be handled.

Rails 4 Create Related Object After Save

I have two models with the [fields]:
Order [:date]
Delivery Slot [:day]
Order belongs_to :delivery_slot
When an order is created, I want a delivery slot to be created with the :day set to the order :date.
So far I have created a new method create_delivery_slots in the Order controller that creates a Delivery Slot when the Order is created, but where I am stumped is, how do I get the Order :date in the Delivery Slot :day field?
#Create delivery slots if they dont already exist
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == #order.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => #order.date)
slot.save!
end
I have tried multiple approaches, but no luck. My gut tells me its something to do with strong parameters but I can't figure it out...
I'm not sure exactly of how you're set up but you'll probably want something like this:
class Order < ActiveRecord::Base
has_a :delivery_slot
after_create => :create_delivery_slots
.
#other code stuffs
.
.
private
def create_delivery_slots
existingslots = []
existingslots = DeliverySlot.all.select {|slot| slot.day == self.date}
if existingslots.empty?
slot = DeliverySlot.new(:day => self.date)
slot.save!
end
end
end
That's untested but it should be basically what you need.

Tracking object changes rails (Active Model Dirty)

I'm trying to track changes on a method just like we are tracking changes on record attributes using the Active Model Dirty.
At the moment I'm using this method to check changes to a set of attributes.
def check_updated_attributes
watch_attrs = ["brand_id", "name", "price", "default_price_tag_id", "terminal_usp", "primary_offer_id", "secondary_offer_id"]
if (self.changed & watch_attrs).any?
self.tag_updated_at = Time.now
end
end
However the attribute price is not a normal attribute with a column but a method defined in the model that combines two different attributes.
This is the method:
def price
if manual_price
price = manual_price
else
price = round_99(base_price)
end
price.to_f
end
Is there a way to track changes on this price method? or does this only work on normal attributes?
Edit: Base price method:
def base_price(rate = Setting.base_margin, mva = Setting.mva)
(cost_price_map / (1 - rate.to_f)) * ( 1 + mva.to_f)
end
cost_price and manual_price are attributes with columns it the terminal table.
Ok, solved it.
I had to create a custom method named price_changed? to check if the price had changed.
def price_changed?
if manual_price
manual_price_changed?
elsif cost_price_map_changed?
round_99(base_price) != round_99(base_price(cost_price = read_attribute(:cost_price_map)))
else
false
end
end
This solved the problem, although not a pretty solution if you have many custom attributes.

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

Rails 3 question - locking row for specific time

I'm building a rails 3 app in which it sells a limited number of items. I'm looking for a way to hold an item for a specific amount of time so that when someone selects an item, they have time to purchase it before someone else can purchase it before them. I have done some research as to row locking but haven't found a usable method thus far for specifying a time.
Thanks for any help or ideas
This is a typical workflow pattern, where you acquire an object for a duration. You can achieve this easily by implementing application level locks.
1) Add lock fields to the model.
locker_id
lock_until
2) Now you can implement this logic in the Product model.
class Product
belongs_to :locker, :class_name => "User",
:condition => lambda { {:conditions => ["lock_until < ? ", Time.now]}}
def locked?
!lock_until.nil? and lock_until > Time.now
end
def lock_for_duration(usr, duration=10.minutes)
return false if locked?
self.locker_id = user.id
self.lock_until = duration.from_now
self.save
end
def release_lock
return true unless locked?
self.locker_id = nil
self.lock_until = nil
self.save
end
end
Here is how to use this:
usr = User.first
product.lock_for_duration(usr, 30.minutes)
product.locked?
product.locker?
I would recommend setting a locked_until timestamp that's checked whenever someone attempts to buy one of these items. If there are no items with a locked_until time in the past, then all items are "sold out". For actual selling of the items, I would have a sold boolean field.

Resources