Trying to refactor my ruby code - ruby-on-rails

I have a score attribute starting from 30 points, and want to deduct 1 point for certain criteria it meets. Here is how I write it in a long way:
case
when user.growth_rate >= 0.1 && user.growth_rate <= 0.11
user.update(score: 29) if user.score != 29
when user.growth_rate >= 0.11 && user.growth_rate <= 0.12
user.update(score: 28) if user.score != 28
end
And the list goes on, Is there a better way/method to do logic like this?

It could be possible to calculate the score from the growth_rate maybe.
score = (30.0 - (user.growth_rate * 10)).floor
user.update(score: score) if user.score != score

Related

Is there a way to perform if-else using collect in ruby and update an array

$days = 25
action_count = [0,0,0]
if $days < 0
action_count[0] += 1
elsif $days <= 20
action_count[1] += 1
else
action_count[2] += 1
end
Can this if-else code be shortened using collect in ruby
I can't think of a way to do this using collect, but you could use Enumerable#bsearch_index.
If $days is an integer, this does the same thing as your if; but it is not very readable. (Integer, because there is a slight problem that you are using two different comparisons, and I'm assuming $days <= 20 is the same as $days < 21).
action_count[[0, 21].bsearch_index { |x| $days < x } || -1] += 1
bsearch_index finds whether $days is lower than 0, 21 or neither, returning 0, 1 or nil. We replace the nil case with -1 (last element), and we have an index we can use to increment an appropriate element of action_count.

Whats wrong with this if statement? Rails

I am building a reputation system where users get points if milestones (10, 100, 1000, ...) are archieved. I have this if statement line:
if (before_points < ((10 || 100 || 1000 || 10000 || 100000 || 1000000))) && (after_points >= ((10 || 100 || 1000 || 10000 || 100000 || 1000000)))
It should return true if the points where either less than 10 or 100 or 1000 ...before, and if the points were more or equal to either 10 or 100 or 1000 ... afterwards.
It works if it was below 10 before, and more than 10 afterwards, and I am not quite sure if it works with 100, but it doesnt work if the points were below 1000 before and more than 1000 afterwards.
Is this the correct way to do this? Is it better to do this with a switch/case?
A more compact way you could do it...
[10, 100, 1000, 10000, 100000, 1000000].any?{|n| before_points < n && after_points >= n}
That expression will return true if a boundary is crossed, or false otherwise
That's not really how logic operation work. The statement:
(10 || 100 || 1000 || 10000 || 100000 || 1000000)
will evaluate to 10. The || operator between 2 or more numbers will return first non-nil value, in this case that's 10, the first value. Related question.
And even if that weren't the case, if the before_points < 10 is true, the before_points < 1000000 would also be true and if only before_points < 1000000 was true, the if statement would still execute just the same as with before_points < 10, so the logic would be wrong.
Depending on what you want to solve, you could either use case or define your milestones in array and iterate values 10,100,...,1000000, setting new milestone each time the condition is still true.
Your assumption is wrong.
if (before_points < ((10 || 100 || ...
will first evaluate the part
10 || 100
which will always return 10 because 10 evaluates to truthy, hence this line
if (before_points < ((10 || 100 || 1000 || 10000 || 100000 || 1000000))) && (after_points >= ((10 || 100 || 1000 || 10000 || 100000 || 1000000)))
is effectively the same of
if (before_points < 10) && (after_points >= 10)
I'm not sure what you want to achieve, but it's probably better to use a case (this is just an example)
case
when before_points < 10 && after_points >= 10
# ...
when before_points < 100 && after_points >= 100
# ...
else
# ...
end

How can I write this conditional in fewer lines?

I wrote this code in my model:
percentage = 0
if self.date_of_birth.present?
percentage += 15
end
if self.gender.present?
percentage += 15
end
if self.relationship_status.present?
percentage += 10
end
if self.language.present?
percentage += 10
end
if self.qualification.present?
percentage += 10
end
if self.interests.present?
if self.interests.count >= 10
percentage += 10
else
percentage += self.interests.count * 5
end
end
But it does not look good. It is a lot of code for a small thing. I want to reduce the number of lines.
You can do it inline, like this:
percentage += 15 if self.date_of_birth.present?
Instead of this:
if self.interests.count >= 10
percentage += 10
else
percentage += self.interests.count*5
end
You can use a ternary operator:
percentage += self.interests.count >= 10 ? 10 : self.interests.count*5
percentage = [
(15 if date_of_birth.present?),
(15 if gender.present?),
(10 if relationship_status.present?),
(10 if language.present?),
(10 if qualification.present?),
((counts = interests.count.to_i) >= 10 ? 10 : (counts * 5)),
].compact.sum
You could use an instance method in your model:
#app/models/model.rb
class Model < ActiveRecord::Base
def percentage
value = 0
values = [[:date_of_birth, 15], [:gender, 15], [:relationship_status,10], [:language,10], [:qualification, 10]]
values.each do |attr,val|
value += val if self.send(attr).present?
end
value += self.interests.count >= 10 ? 10 : self.interests.count*5 if self.interests.present?
# Rails should return the value of the last line, which is the "value" var
end
end
This would allow you to use #user.percentage, where #user is your instance var for the model.
Personally, I don't think that "less lines" is a good idea, but if you want your code in less lines, you can write it like this:
percentage = 0; if date_of_birth.present? then percentage += 15 end; if gender.present? then percentage += 15 end; if relationship_status.present? then percentage += 10 end; if language.present? then percentage += 10 end; if qualification.present? then percentage += 10 end; if interests.present? then if interests.count >= 10 then percentage += 10 else percentage += interests.count*5 end end
In Ruby, you can (almost) always replace linebreaks with semicolons to make your code fit on less lines. In fact, every Ruby program can always be written on a single line.
inc_att = ["date_of_birth", "gender", "relationship_status" , "language", "qualification", "interests"]
inc_att.each do |s|
if self[s].present? && (s == "date_of_birth" || s == "gender")
percentage += 15
elsif self[s].present? && s == "interests" && self[s].count < 10
percentage += self[s].count * 5
else
percentage += 10 if self[s].present?
end
end
Have a look into it
inc_att = ["date_of_birth", "gender", "relationship_status" , "language", "qualification", "interests"]
inc_att.each do |s|
if self[s].present? && (s == "date_of_birth" || s == "gender")
percentage += 15
elsif self[s].present? && s == "interests" && self[s].count < 10
percentage += self[s].count * 5
else
percentage += 10 if self[s].present?
end
end
Inspired by #sawa's answer:
counts = interests.count.to_i
percentage = (counts >= 10 ? 10 : (counts * 5)) +
[
date_of_birth.present? && 15,
gender.present? && 15,
relationship_status.present? && 10,
language.present? && 10,
qualification.present? && 10,
].select(&:itself).sum

using nested case statements to return value

I'm trying to use case statements to have a value returned, depending on the conditions. Unfortunately I'm getting a string of syntax errors because I'm not completely familiar with how a case statement should be set up.
Basically, a user can belong to a single state (region), so in the user table is where the user's "state_id" is stored (53 could be Oregon for example). I had most of this working using if statements, but it was far too messy, so I was told "case" would be the better way to go. I also have a model "tax" which belongs_to a user and a user can have many of.
Prior to changing to using case, I was doing if user.state_id == 53 for example, which was working, and then the conditions were if self.income <= 10276 which also worked. Now with the case however, although slightly more refactored, I can't get it working.. (many syntax errors due to the evaluators <= and all the others.)
def provincial
case user.state_id
when 53
case income
when <= 10276
return 0
when 10277..37568
return self.income * 0.0506
when 37569..75138
return ((self.income - 37568) * 0.077) + 1901
when 75139..86268
return ((self.income - 75138) * 0.105) + 4794
when 86269..104754
return ((self.income - 86268) * 0.1229) + 5963
when > 104754
return ((self.income - 104754) * 0.147) + 8235
end
when 52
case income
when <= 17593
return 0
when > 17593
return self.income * 0.10
end
end
end
You can't put the variable at the head of the case and then use a "dangling" inequality (an inequality where you only have an expression on one side). You would need the variable with the inequality, or you could use a proc.
So this when clause is fine:
when 10277..37568
return self.income * 0.0506
But this one isn't:
when > 17593
return self.income * 0.10
You can, however, replace it with this:
when proc {|n| n > 17593}
return self.income * 0.10
This gets rid of the "dangling" inequality by using a proc. You might also be able to replace it with this (a little weird, but it works):
Inf = 1.0/0 # Assigns "infinity" to Inf
when 17593..Inf
return self.income * 0.10
You could also move the variable to each when:
def provincial
case user.state_id
when 53
case
when income <= 10276
return 0
when (income >= 10277) and (income <= 37568)
return self.income * 0.0506
when (income >= 37569) and (income <= 75138)
return ((self.income - 37568) * 0.077) + 1901
...
end
when 52
case
when income <= 17593
return 0
when income > 17593
return self.income * 0.10
end
end
end
end
Overall, in this situation, I think I like the proc best. :)
Note you can't mix, in the same case statement, the variable at the head of the case and the variable at the when clauses. So this is not valid:
case x
when x < 3
# do stuff
when 5
# do stuff
end

checking user model variable using nested if statements

I'm trying to perform a check on my User model using a series of nested if statements. I have two models, User, which has_many :taxes, and tax which belongs_to :user. Within a column of the user table, each user has a specific "state id" which they select when they sign up (for example, state_id: 53 could mean California). Anyway, in my Tax model, I have several if statements which evaluate to see if a user is from a specific region, and if true, the conditions inside the if are performed.
For example:
if user_state_id == 53
# Do this
end
The problem is, if the user isn't a match to the first if statement, it doesn't continue to check the remaining statements to see if it matches any of the others. (See Below). If this user had a state_id of 53, it would work normally and run the remaining conditions inside. However, if the user had a state_id of 52 (next in the list) it would not evaluate.
def provincial
if user.state_id == 53 #BC
if calculation = self.income <= 10276
return 0
elsif calculation = self.income > 10276 && self.income <= 37568
return self.income * 0.0506
end
end
if user.state_id == 52 #AB
if calculation = self.income <= 17593
return 0
elsif calculation = self.income < 17593
return self.income * 0.10
end
end
if user.state_id == 60 #ON
if calculation = self.income <= 9574
return 0
elsif calculation = self.income > 9574 && self.income <= 39723
return self.income * 0.0505
end
end
end
I also tried using elsif statements for the primary nested condition checks (elsif user.state_id == 52...) but that did not work either.
I just posting a review of your code by using nested case statements.Sorry for the syntax errors(if any)
case user.state_id
when 53 #BC
case calculation
when <=10276
return 0
when 10277..37568
return self.income * 0.0506
when 52 #AB
case calculation
when 17593
return 0
when < 17593
return self.income * 0.10
when 60 #ON
case calculation
when <= 9754
return 0
when 9575..39793
return self.income * 0.0505
Hope it helps!
In your nested if statement for state_id == 52 you are checking twice whether income is smaller then 17593 (in both if and elseif).
if user.state_id == 52 #AB
if calculation = self.income <= 17593
return 0
elsif calculation = self.income < 17593 # shouldn't it be > 17593?
return self.income * 0.10
end
end
Also you are not covering all the cases! If state_id is equal to 53, and income is bigger than 37568, this will return nil.
Another thing, if you checked in you if statement that self.income <= 10276 (self is not needed btw), you don't need to recheck it again in elsesif (elseif self.income > 10276). There is no other option.
Your calculation variable is completely obsolete - your function returns if it is true and continues when its false.

Resources