checking user model variable using nested if statements - ruby-on-rails

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.

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.

How to reduce 'complexity too high' with || - or operator

I've got a simple method that counts total lesson hours in the university schedule for additional modules in the department (students can attend many departments)
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
h[department] = (sports_hours[department] || 0) +
(science_hours[department] || 0) +
(intership_sum[department] || 0) +
(art[department] || 0) -
((obligatory_topics[department] || 0) +
(base[department] || 0))
end
end
How can I fix here Cyclomatic complexity for hours_total is too high.? I have no idea how to not repeat || 0 cause in some departments sports_hours[department] can be nil value
The first step I'd take
def hours_total
#hours_total = user.open_departments.each_with_object({}) do |department, h|
positive = [sport_hours, science_hours, internship_sum, art].sum do |pos_h|
pos_h[department].to_i
end
negative = [obligatory_topics, base].sum do |neg_h|
neg_h[department].to_i
end
h[department] = positive - negative
end
end
Note: if your hours can be float values, substitute to_i with to_f.
Now if you and your Rubocop are ok with that, I'd probably leave it. If any of you is unhappy, the positive and negative should be extracted to a method.

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

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

all values same sign validation

User should insert all the values either positive or negative.
How may i set same sign validation ?
Right i have written this on before_save ..
unless (self.alt_1 >= 0 && self.alt_2 >=0 && self.alt_3 >= 0 &&
self.alt_4 >= 0 && self.alt_5 >= 0 && self.alt_6 >= 0) ||
(self.alt_1 <= 0 && self.alt_2 <=0 && self.alt_3 <= 0 &&
self.alt_4 <= 0 && self.alt_5 <= 0 && self.alt_6 <= 0)
self.errors.add_to_base(_("All values sign should be same."))
end
first_sign = self.alt_1 <=> 0
(2..6).each do |n|
unless (self.send("alt_#{n}") <=> 0) == first_sign
errors.add_to_base(_("All values' signs should be same."))
break
end
end
With this method we first get the sign of alt_1, and then see if the signs of the rest of the elements (alt_2 through alt_6) match. As soon as we find one that doesn't match we add the validation error and stop. It will run a maximum of 6 iterations and a minimum of 2.
Another more clever, but less efficient method, is to use the handy method Enumerable#all?, which returns true if the block passed to it returns true for all elements:
range = 1..6
errors.add_to_base(_("All values' signs should be same.")) unless
range.all? {|n| self.send("alt_#{n}") >= 0 } ||
range.all? {|n| self.send("alt_#{n}") <= 0 }
Here we first check if all of the elements are greater than 0 and then if all of the elements are less than 0. This method iterates a maximum of 12 times and a minimum of 6.
Here's a slightly different approach for you:
irb(main):020:0> def all_same_sign?(ary)
irb(main):021:1> ary.map { |x| x <=> 0 }.each_cons(2).all? { |x| x[0] == x[1] }
irb(main):022:1> end
=> nil
irb(main):023:0> all_same_sign? [1,2,3]
=> true
irb(main):024:0> all_same_sign? [1,2,0]
=> false
irb(main):025:0> all_same_sign? [-1, -5]
=> true
We use the spaceship operator to obtain the sign of each number, and we make sure that each element has the same sign as the element following it. You could also rewrite it to be more lazy by doing
ary.each_cons(2).all? { |x| (x[0] <=> 0) == (x[1] <=> 0) }
but that's less readable in my opinion.
unless
[:<=, :>=].any? do |check|
# Check either <= or >= for all values
[self.alt1, self.alt2, self.alt3, self.alt4, self.alt5, self.alt6].all? do |v|
v.send(check, 0)
end
end
self.errors.add_to_base(_("All values sign should be same."))
end

Resources