Ruby operators - formula - ruby-on-rails

I'm trying to make a formula in my project.rb model in my rails 4 app.
I have an attribute in an preference table, called delay. I want to calculate whether the tolerance on one user's part, is close to the delay required by another user.
In my project.rb, I've tried to do this as follows:
def publication_delay_variance
if #current_user.profile.organisation.preference.delay >= #project.profile.organisation.preference.delay
'No problems here'
elsif #current_user.profile.organisation.preference.delay * 90% >= #project.profile.organisation.preference.delay
"Close, but not quite there"
else #current_user.profile.organisation.preference.delay * 50% >= #project.profile.organisation.preference.delay
"We're not in alignment here"
end
end
the current user is the current user who is currently logged in and interacting with the page. The other user is the user who created the project. Each user has an organisation. Each organisation has preferences. I'm trying to compare them.
Can anyone see what I'm doing wrong? I don't have much experience with this. My current attempt generates this error:
syntax error, unexpected >=
...ence.publication_delay * 90% >= #project.profile.organisatio...
..

The problem is that 90% isn't valid in Ruby. You probably meant to use 0.9 instead. Also, your last else should be an elsif:
def publication_delay_variance
if #current_user.profile.organisation.preference.delay >= #project.profile.organisation.preference.delay
'No problems here'
elsif #current_user.profile.organisation.preference.delay * 0.9 >= #project.profile.organisation.preference.delay
"Close, but not quite there"
elsif #current_user.profile.organisation.preference.delay * 0.5 >= #project.profile.organisation.preference.delay
"We're not in alignment here"
end
end
Of course, without an else you don't have a default case, so you should consider what behavior you want if none of those three conditions is true.
P.S. You can make this a lot more readable by assigning those values to local variables with shorter names:
def publication_delay_variance
user_delay = #current_user.profile.organisation.preference.delay
project_delay = #project.profile.organisation.preference.delay
if user_delay >= project_delay
"No problems here"
elsif user_delay * 0.9 >= project_delay
"Close, but not quite there"
elsif user_delay * 0.5 >= project_delay
"We're not in alignment here"
end
end
P.P.S. 0.9 and 0.5 are magic numbers. Consider moving their values into constants.

In Ruby, % is the modulus operator, which takes two arguments x % y and returns the remainder of x / y. It doesn't make sense to have >= immediately after it, which is what the error message is telling you. To represent a percentage in Ruby, use a decimal number, eg 0.9.

Related

How to refactor huge lookup table in Ruby

I have a method:
def assign_value
...
#obj.value = find_value
end
and a huge lookup table:
def find_value
if #var > 0 && #var <= 30
0.4
elsif #var > 30 && #var <= 50
0.7
elsif #var > 50 && #var <= 70
1.1
elsif #var > 70 && #var <= 100
1.5
elsif #var > 100 && #var <= 140
2.10
elsif #var > 140 && #var <= 200
2.95
elsif #var > 200 && #var <= 300
4.35
elsif #var > 300 && #var <= 400
6.15
elsif #var > 400 && #var <= 500
7.85
elsif #var > 500 && #var <= 600
9.65
...
end
and so on for 1800 lines.
Needless to say, it's for the tax department of an unnamed country. Right now, it's written manually (all 1800 lines of it) to account the varying length of the integer ranges and decimal return values.
Without rewriting an entire country's tax code, how would I refactor this?
The ranges and values do change on a yearly basis, and we need to maintain backwards compatibility. A database table would simplify matters, but because we're dealing with a number of different countries, each with different requirements and tax codes, creating a database table isn't as straightforward as it sounds.
#MilesStanfield's answer has pretty much the same impact as yours. As said in my comment, I would use a hash to get rid of all conditions (or cases):
COUNTRY_TAX_RATES = {
0..30 => 0.4,
31..50 => 0.7,
51..70 => 1.1
}
COUNTRY_TAX_RATES[0..30] # 0.4
COUNTRY_TAX_RATES.select { |code| code === 10 }.values.first # 0.4
If you don't change the codes often you could use this. If it does change frequently you might consider using another approach (for example, store in db and manage through an user interface).
UPDATE: Now I'm no longer on my mobile I want to expand this answer a little bit further. It seems you are disregarding #Wand Maker's proposal, but I don't think you should.
Imagine you use my hash. It is convenient, without any conditionals and easy to adjust. However, as Wand Maker points out, everytime either the range or decimal changes you need a developer to update the code. This might be ok, because you only have to do it on a yearly basis, but it is not the cleanest approach.
You want accountants to be able to update the tax rates and codes, instead of developers. So you should probably create a table which contains these attributes. I'm not sure what the ranges and decimals stand for in your example, but I hope you get the idea:
Tax (ActiveRecord) with range_column (Give this an explicit, explanatory name. You could also use min and max range columns), code, rate and country_id
class Tax < ActiveRecord::Base
serialize :range_column
belongs_to :country
end
Country has_many :taxes
If you want to know the tax rate for malta (country_id: 30), with tax code 40 (whatever this might mean) you could do something like this:
Tax.select(:tax_rate).joins(:country).find_by(country_id: 30, range_column: 40).tax_rate # you might need a between statement instead, can't check right now if querying a serialized hash works like this
Now an accountant can update the ranges, decimals or any other attribute when these change (of course you have to build a CRUD for it first).
P.s. Don't mind the naming, not sure what these numbers represent. :)
I would use a case statement with ranges
case #var
when 0..30 then 0.4
when 31..50 then 0.7
when 51..70 then 1.1
...
end
Suppose inc is income and arr is an array of pairs [bp, tax], where
arr[0][0] = 0, meaning bp (income "breakpoint") for the first element of arr is zero;
arr[i][0] < arr[i+1][0], meaning breakpoints are increasing;
tax arr[-1][1] is payable if inc >= arr[-1][0]; and
tax arr[i][1] is payable if arr[i][0] <= inc < arr[i+1][0], 0 <= i <= arr.size-2.
Tax can then be computed
arr.find { |bp,_| inc >= bp }.last
since the breakpoints are increasing.

Is a point in time: within an hour, within today, tomorrow later than tomorrow or past already?

I want to display a closing time in relative words, e.g. "You have 2 hours left to answer the poll".
What is wrong with the below? When there are e.g. 54 minutes left, it returns "54 hours".
minute_diff = ((datetime_value - DateTime.current) * 24 * 60).to_i
case
when minute_diff < 0 # past time
[nil, "closed"]
when minute_diff.between?(0, 59) # within an hour
[minute_diff, "minutes"]
when datetime_value.today? # today
number_of_hours = (minute_diff / 60)
[number_of_hours, "hours"]
when datetime_value.to_date == DateTime.current.tomorrow.to_date # tomorrow
[1, "day"]
when datetime_value.to_date > DateTime.current.tomorrow.to_date # later than tomorrow
number_of_days = (datetime_value.to_date - DateTime.current.to_date).to_i
[number_of_days, "days"]
end
(Suggestions on a better way to return relative time is much appreciated)
Regarding your second question (giving a better way), you should use what is already there.
if (t = DateTime.current) < datetime_value
"You have #{distance_of_time_in_words(t, datetime_value)} left to answer the poll".
else
"Closed"
end
If you want to handle l18n, my favorite is the "twitter-cldr" gem.
I think that the rails date helpers ARE sufficient for this.
if datetime_value > 0
s = "You have #{time_ago_in_words(datetime_value)} left to answer the poll"
else
s = "Closed"
end

BigDecimal to Currency with -0.0

I am working on reports for a website and I am currently thinking of what would be the best way to handle BigDecimal -0.0's.
The database I'm working with has a lot of them. When these -0.0's are put through number_to_currency(), I get "$-0.00". My format for negative numbers is actually "-$x.xx", so note that number_to_currency is not formatting it as a negative number (otherwise there would also be a negative sign in front of the dollar sign), but for some reason the negative sign is being translated along with the 0.
Right now my solution is to do this every time I get an amount from the database:
amount *= -1 if amount == 0 && amount.sign == -1
This changes the -0.0 to a 0.0. It's simple enough, but I can't help but wonder if there is a better solution, or something on BigDecimals or number_to_currency to handle this situation that I'm just not finding.
That is so because the number is converted into a string to be displayed. And:
# to_d converts to BigDecimal, just FYI
"-0".to_d.to_s #=> "-0.0"
Therefore you will have to make it a 0 yourself. But the sign-checks are redundant - a simple comparison with 0 will do the trick:
bdn = "-0".to_d # or BigDecimal.new("-0")
value = bdn.zero? ? 0 : bdn
number_to_currency(value, other_options)
However, you wouldn't want to manually add this check everywhere you're calling number_to_currency. It would be more convenient to create your own modified_number_to_currency method, in your ApplicationHelper, like so:
def modified_number_to_currency( number, options )
value = number.zero? ? 0 : number
number_to_currency(value, options)
end
And then use modified_number_to_currency instead of number_to_currency.
Alternatively, you could overwrite number_to_currency and have it call super in the end. That might also work but I'm not 100% certain.
Coming to your check specifically:
amount *= -1 if amount == 0 && amount.sign == -1
It should simply be:
amount = 0.to_d if amount.zero? # the to_d might or might not be required

Generating Random Fixed Decimal in Rails

I'm trying to generate random data in my rails application.
But I am having a problem with decimal amount. I get an error
saying bad value for range.
while $start < $max
$donation = Donation.new(member: Member.all.sample, amount: [BigDecimal('5.00')...BigDecimal('200.00')].sample,
date_give: Random.date_between(:today...Date.civil(2010,9,11)).to_date,
donation_reason: ['tithes','offering','undisclosed','building-fund'].sample )
$donation.save
$start +=1
end
If you want a random decimal between two numbers, sample isn't the way to go. Instead, do something like this:
random_value = (200.0 - 5.0) * rand() + 5
Two other suggestions:
1. if you've implemented this, great, but it doesn't look standard Random.date_between(:today...Date.civil(2010,9,11)).to_date
2. $variable means a global variable in Ruby, so you probably don't want that.
UPDATE --- way to really get random date
require 'date'
def random_date_between(first, second)
number_of_days = (first - second).abs
[first, second].min + rand(number_of_days)
end
random_date_between(Date.today, Date.civil(2010,9,11))
=> #<Date: 2012-05-15 ((2456063j,0s,0n),+0s,2299161j)>
random_date_between(Date.today, Date.civil(2010,9,11))
=> #<Date: 2011-04-13 ((2455665j,0s,0n),+0s,2299161j)>

Ruby on Rails random number not working

I've been at this for awhile. I am building a simple lottery website and I am generating random tickets. On my local machine random numbers are generated, however, on the server they are duplicated.
I have tried multiple versions of what I have, but the duplicates is the same.
I need to create a random ticket number per ticket and ensure that it hasn't bee created.
This my like 50th version:
a = Account.find(current_account)
numTics = params[:num_tickets].to_i
t = a.tickets.where(:item_id => item.id).count
total = t + numTics
if total > 5
left = 5 - t
flash[:message] = "The total amount of tickets you can purchase per item is five. You can purchase #{left} more tickets."
redirect_to buy_tickets_path(item.id)
else
i = 1
taken = []
random = Random.new
taken.push(random.rand(100.10000000000000))
code = 0
while i <= numTics do
while(true)
code = random.rand(100.10000000000000)
if !taken.include?(code)
taken.push(code)
if Ticket.exists?(:ticket_number => code) == false
a.tickets.build(
:item_id => item.id,
:ticket_number => code
)
a.save
break
end
code = 0
end
end
i = i + 1
end
session['item_name'] = item.name
price = item.price.to_i * 0.05
total = price * numTics
session['amount_due'] = total.to_i
redirect_to confirmation_path
end
You should be using SecureRandom if possible, not Random. It works the same way but is much more random and doesn't need to be initialized like Random does:
SecureRandom.random_number * 100.1
If you're using Ruby 1.8.7 you can try the ActiveSupport::SecureRandom equivalent.
Also if you're generating lottery tickets, you will want to make sure your generator is cryptographically secure. Generating random numbers alone is probably not sufficient. You will likely want to apply some other function to generate these.
Keep in mind that most actual lotteries do not generate random tickets at the point of purchase, but generate large batches in advance and then issue these to purchasers. This means you are able to preview the tickets and ensure they are sufficiently random.
The problem is not with Ruby's pseudo random number generator but that fact that you are creating generators all the time with Random.new. As explained in this answer, you should not have to call Random.new more than once. Store the result in a global object and you'll be good to go.

Resources