Can I periodically recalculate an attribute after a given time interval? - ruby

I have a model "User", with a float attribute "Value". I want each User's Value to depreciate 1%(i.e., be multiplied by 0.99) every 24 hours after the User's creation. For example, if a User is created with a Value of 5 at 02:34 on January 1st, then at 02:34 on January 2nd, its value should be recalculated to be 4.95. Then at 02:34 on January 3rd, it should be recalculated to be 4.9005, and so on.
Can this be done?
I'm using a Rails 4.0.10 app, if that matters.

With the delayed_job gem you could do something like this:
# in user.rb
after_create :decrease_value_periodically
private
def decrease_value_periodically
self.value = value * 0.99
self.save!
decrease_value_periodically if value > 0
end
handle_asynchronously :decrease_value_periodically,
:run_at => Proc.new { 24.hours.from_now }
This example would run 24 hours after creation of an user and would reschedule itself over and over again.
Another option might be a daily Cron task that recalculates all user's values at the same time (midnight for example). You could setup a Cron task that runs a Rails method like this:
# command for Cron
rails runner "User.recalculate_all_values"
# in user.rb
def self.recalculate_all_values
User.all.each { |user| user.decrease_value }
end
private
def decrease_value
if value > 0
self.value = value * 0.99
self.save!
end
end
IMO it depends on your priorities: If you want a precise run after 24 hours, choose delayed_jobs. Whereas the Cron solution is easier to maintain, but you might end up with decreasing values to early or to late (Imagine a user is created at 11pm and one hour later the Cron task starts to decrase the value for the first time).

Related

sending emails after every 2 minute to the email addresses from excelsheet

Want to send emails after every 2 minute to the email addresses from excelsheet.
I tried using sidekiq and delayed_job but emails are shooting after a delay but at same time.
Tried delay, delay_for and some methods but not helping
worker file
class MarketingEmailsWorker
include Sidekiq::Worker
def perform(*args)
EmailList.read_file(args)
end
end
EmailList.rb model
def self.read_file(record)
list = EmailList.find(record).last
spreadsheet = Roo::Spreadsheet.open(list.file.path, extension: :xlsx)
header = spreadsheet.row(1)
(2..spreadsheet.last_row).each do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
email = row["Email"]
if email.present?
geography= row["Geography"].to_s
lname = row["Name"]
designation = row["Designation"]
Notifier.send_template_mail(geography,email,lname,designation,list.emails_template).deliver_now
end
end
end
Codewise there is not really a viable option just something weird like
def perform(*args)
while true do
EmailList.read_file(args)
sleep(120) # 2 minutes in seconds
end
end
But I don't recommend it for any production system. Since you can't really control that worker.
Better way
of solving this would be using some scheduler
There you can setup a yml with cron like sidekiq workers.
There are plenty of options to get your workers scheduled e.g. cron: '*/2 * * * *' or every: '2m'.
There is also a scheduler option for delayed_job with a solid documentation.
Take in to account that as soon your worker takes longer than 2 minutes to process you will pile up workers in your queue.

How to limit the number of votes in 'acts_as_votable' gem

I'm currently using the rubygem acts_as_votable as the voting system in my project, but I want to limit the total votes of every user (which I used rubygem devise)
The target of the vote is a model called Pin:
class Pin < ActiveRecord::Base
acts_as_votable
end
Should I use a method and put it in the before_action: to make sure that the vote your making won't let your total votes exceed say like 10?
Updated: 8/18/2015
Now I popped up with a new question:
I created another model group, and declaim the relationship:
(group.rb)
has_many: pins
(pin.rb)
belongs_to: group
So, here comes up the question, if I want to limit the votes in every group, say like: 10 in group 1, 10 in group 2, 10 in group 3....
How can I accomplish it?
You can do something like this:
def upvote
#pin = Pin.find(params[:id])
# check for user's total votes
if current_user.find_voted_items.size < 10
#pin.vote_by :voter => current_user
else
..... #your code
flash[:notice] = "your total votes exceed"
redirect_to pins_path
end
end

How do I represent morning and midnight timings in ruby?

I need to store and retrieve restaurant timings in simple methods like morning and midnight. What I am doing now is:
def morning
Time.new("6:30 am")
end
def midnight
Time.new("12:00 am")
end
I can compare timings now but this seems to be the wrong way to do it and then I don't know how to read those time values back for a method like:
def open?(time)
time >= morning && time <= midnight
end
What is the right way to do this?
Using the chronic gem I was able to do this:
def opens_at
morning
end
def closes_at
midnight
end
def open?(time)
Chronic.parse(time) >= morning && Chronic.parse(time) < midnight
end
private
def morning
Chronic.parse("6:30 am")
end
def midnight
Chronic.parse("midnight")
end
This works for comparisons
For people like me who do not like having a extra gem in gemfile for smaller things, will go for:
def morning
DateTime.now.beginning_of_day + (6.5).hours
end
def midnight
DateTime.now.end_of_day
# Or DateTime.now.at_midnight + 1
end
there are a lot more options DateTime gives us like:
:at_beginning_of_day, :at_beginning_of_month, :at_beginning_of_quarter, :at_beginning_of_week, :at_beginning_of_year, :at_end_of_month, :at_end_of_quarter, :at_end_of_week, :at_end_of_year, :at_midnight, :awesome_inspect, :awesome_print, :beginning_of_day, :beginning_of_month, :beginning_of_quarter, :beginning_of_week, :beginning_of_year, :wday, :sunday?, :monday?, :tuesday?, :wednesday?, :thursday?, :friday?, :saturday?
Just do( trust me you wont regret it):
DateTime.now.methods.sort

How do you have threads in Ruby send strings back to a parent thread

I want to be able to call a method that repeats x amount of times on a separate thread that sends messages such as "still running" every few moments to the console while I am free to call other methods that do the same thing.
This works in my test environment and everything checks out via rspec - but when I move the code into a gem and call it from another script, it appears that the code is working in additional threads, but the strings are never sent to my console (or anywhere that I can tell).
I will put the important parts of the code below, but for a better understanding it is important to know that:
The code will check stock market prices at set intervals with the intent of notifying the user when the value of said stock reaches a specific price.
The code should print to the console a message stating that the code is still running when the price has not been met.
The code should tell the user that the stock has met the target price and then stop looping.
Here is the code:
require "trade_watcher/version"
require "market_beat"
module TradeWatcher
def self.check_stock_every_x_seconds_for_value(symbol, seconds, value)
t1 = Thread.new{(self.checker(symbol, seconds, value))}
end
private
def self.checker(symbol, seconds, value)
stop_time = get_stop_time
pp stop_time
until is_stock_at_or_above_value(symbol, value) || Time.now >= stop_time
pp "#{Time.now} #{symbol} has not yet met your target of #{value}."
sleep(seconds)
end
if Time.now >= stop_time
out_of_time(symbol, value)
else
reached_target(symbol, value)
end
end
def self.get_stop_time
Time.now + 3600 # an hour from Time.now
end
def self.reached_target(symbol, value)
pp "#{Time.now} #{symbol} has met or exceeded your target of #{value}."
end
def self.out_of_time(symbol, value)
pp "#{Time.now} The monitoring of #{symbol} with a target of #{value} has expired due to the time limit of 1 hour being rached."
end
def self.last_trade(symbol)
MarketBeat.last_trade_real_time symbol
end
def self.is_stock_at_or_above_value(symbol, value)
last_trade(symbol).to_f >= value
end
end
Here are the tests (that all pass):
require 'spec_helper'
describe "TradeWatcher" do
context "when comparing quotes to targets values" do
it "can report true if a quote is above a target value" do
TradeWatcher.stub!(:last_trade).and_return(901)
TradeWatcher.is_stock_at_or_above_value(:AAPL, 900).should == true
end
it "can report false if a quote is below a target value" do
TradeWatcher.stub!(:last_trade).and_return(901)
TradeWatcher.is_stock_at_or_above_value(:AAPL, 1000).should == false
end
end
it "checks stock value multiple times while stock is not at or above the target value" do
TradeWatcher.stub!(:last_trade).and_return(200)
TradeWatcher.should_receive(:is_stock_at_or_above_value).at_least(2).times
TradeWatcher.check_stock_every_x_seconds_for_value(:AAPL, 1, 400.01)
sleep(2)
end
it "triggers target_value_reahed when the stock has met or surpassed the target value" do
TradeWatcher.stub!(:last_trade).and_return(200)
TradeWatcher.should_receive(:reached_target).exactly(1).times
TradeWatcher.check_stock_every_x_seconds_for_value(:AAPL, 1, 100.01)
sleep(2)
end
it "returns a 'time limit reached' message once a stock has been monitored for the maximum of 1 hour" do
TradeWatcher.stub!(:last_trade).and_return(200)
TradeWatcher.stub!(:get_stop_time).and_return(Time.now - 3700)
TradeWatcher.check_stock_every_x_seconds_for_value(:AAPL, 1, 100.01)
TradeWatcher.should_receive(:out_of_time).exactly(1).times
sleep(2)
end
end
And here is a very simple script that (in my understanding) should print "{Time.now} AAPL has not yet met your target of 800.54." every 1 second that the method is still running and should at least be visible for 20 seconds (I test this using sleep in rspec and am able to see the strings printed to the console):
require 'trade_watcher'
TradeWatcher.check_stock_every_x_seconds_for_value(:AAPL, 1, 800.54)
sleep (20)
However I get no output - although the program does wait 20 seconds to finish. If I add other lines to print out to the console they work just fine, but nothing within the thread triggered by my TradeWatcher method call actually work.
In short, I'm not understanding how to have threads communicate with each other appropriately - or how to sync them up with each other (I don't think thread.join is appropriate here because it would leave the main thread hanging and unable to accept another method call if I chose to send one at a time in the future). My understanding of Ruby multithreading is weak anyone able to understand what I'm trying to get at here and nudge me in the right direction?
It looks like the pp function is simply not yet loaded by ruby when you go to print. By adding:
require 'pp'
to the top of trade_watcher.rb I was able to get the output you're expecting. You might also want to consider adding:
$stdout.sync = $stderr.sync = true
to your binary/executable script so that your output is not buffered internally by the IO class and instead passed directly to the os.

Does Ruby have an equivalent to TimeSpan in C#?

In C# there is a TimeSpan class. It represents a period of time and is returned from many date manipulation options. You can create one and add or subtract from a date etc.
In Ruby and specifically rails there seems to be lots of date and time classes but nothing that represents a span of time?
Ideally I'd like an object that I could use for outputting formatted dates easily enough using the standard date formatting options.
eg.
ts.to_format("%H%M")
Is there such a class?
Even better would be if I could do something like
ts = end_date - start_date
I am aware that subtracting of two dates results in the number of seconds separating said dates and that I could work it all out from that.
You can do something similar like this:
irb(main):001:0> require 'time' => true
irb(main):002:0> initial = Time.now => Tue Jun 19 08:19:56 -0400 2012
irb(main):003:0> later = Time.now => Tue Jun 19 08:20:05 -0400 2012
irb(main):004:0> span = later - initial => 8.393871
irb(main):005:0>
This just returns a time in seconds which isn't all that pretty to look at, you can use the strftime() function to make it look pretty:
irb(main):010:0> Time.at(span).gmtime.strftime("%H:%M:%S") => "00:00:08"
Something like this? https://github.com/abhidsm/time_diff
require 'time_diff'
time_diff_components = Time.diff(start_date_time, end_date_time)
No, it doesn't. You can just add seconds or use advance method.
end_date - start_date will have Float type
In the end I forked the suggestion in #tokland's answer. Not quite sure how to make it a proper gem but it's currently working for me:
Timespan fork of time_diff
Not yet #toxaq... but I've started something!
https://gist.github.com/thatandyrose/6180560
class TimeSpan
attr_accessor :milliseconds
def self.from_milliseconds(milliseconds)
me = TimeSpan.new
me.milliseconds = milliseconds
return me
end
def self.from_seconds(seconds)
TimeSpan.from_milliseconds(seconds.to_d * 1000)
end
def self.from_minutes(minutes)
TimeSpan.from_milliseconds(minutes.to_d * 60000)
end
def self.from_hours(hours)
TimeSpan.from_milliseconds(hours.to_d * 3600000)
end
def self.from_days(days)
TimeSpan.from_milliseconds(days.to_d * 86400000)
end
def self.from_years(years)
TimeSpan.from_days(years.to_d * 365.242)
end
def self.diff(start_date_time, end_date_time)
TimeSpan.from_seconds(end_date_time - start_date_time)
end
def seconds
self.milliseconds.to_d * 0.001
end
def minutes
self.seconds.to_d * 0.0166667
end
def hours
self.minutes.to_d * 0.0166667
end
def days
self.hours.to_d * 0.0416667
end
def years
self.days.to_d * 0.00273791
end
end

Resources