ternary operator based on if else - ruby

If i have following if else statement
if a.present? && b.value == 'N'
b = test
elsif a.present? && b.value == 'Y'
b = guest
end
I can write ternary operation for this
b = (a.present? && b.value == 'N') ? "test" : "guest"
but in this ternary oprator i am not looking for condition b.value == 'Y' and it could be something else 'd' or 'e'.
How do i update ternary operator so it verifies both conditions in if and elsif?

For something like this you might want to use a simple look-up table to eliminate some of the logic:
EQUIVALENT = {
'Y' => 'guest',
'N' => 'test'
}
if (a.present?)
b = EQUIVALENT[b.value] || b
end
The || b part may not be necessary if non-mapped b values are ignored.

b = case b.value
when 'N' then test
when 'Y' then guest
end if a.present?
This is the only DRY answer here so far.

You can use a ternary operator. It doesn't mean you should do it, though:
a.present? && (b.value == 'N' ? b = 'test' : b.value == 'Y' && b = 'guest')
Here's a small test:
class Object
def present?
true
end
end
class NilClass
def present?
false
end
end
a = true
class B
attr_accessor :value
end
b = B.new
b.value = 'Y'
a.present? && (b.value == 'N' ? b = 'test' : b.value == 'Y' && b = 'guest')
p b
# "guest"

I would not insist on the ternary operator but extract the common a.present? test in an outer if and then write the rest of the code using if modifiers:
if a.present?
b = test if b.value == 'N'
b = guest if b.value == 'Y'
end
To me, it seems much easier to read this way.

Related

Rails ActionController::Parameters sanitize

I need to sanitize the strong parameters in Rails5 in Rails 4 I used this:
def forest_hash(hash)
new_hash = hash.deep_dup
new_hash.each do |k, v|
new_hash[k] =
if v.is_a?(Hash)
forest_hash(v)
# elsif v.respond_to?(:to_unsafe_h)
# forest_hash(v.to_unsafe_h)
elsif v.is_a?(String) && DATE_TIME_REGEXP === v
v = Time.zone.parse(v)
elsif v == ''
nil
elsif v == ['']
[]
elsif v == '<p> </p>'
nil
elsif v == 'true'
true
elsif v == 'false'
false
elsif v.is_a?(String) && v.to_i.to_s == v
v.to_i
elsif v.is_a?(Array) && v.count > 1
v.delete_if { |x| x == '' }
elsif v.is_a?(String) && base_helpers.strip_tags(v) != v
Foresttrees::TagSanitizer.new(v).sanitize
elsif v.is_a?(String)
v.squish
else
v
end
if new_hash[k].is_a?(Array) && new_hash[k].all? { |vv| vv.is_a?(Hash) }
binding.pry
new_hash[k].map! { |vv| forest_hash(vv) }
end
end
end
<ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"XXXXXXX", "commit"=>"Save", "activism_tree"=><ActionController::Parameters {"name"=>"qewrqewr", "reason"=>"", "affected_locations"=>["", "global"], "risk"=>"3", "description"=>"<p>qerqewr</p>", "published_at"=>"", "published_internet_at"=>"", "related_tree_ids"=>[""], "target_customer_ids"=>["", "5f75952427e4984019f6e9d4"], "monitored_source"=>"false", "cuter"=>"qerqewrqewr", "cut_duration"=>"", "expected_date"=>"", "cut_probability"=>"", "place"=>"", "hour"=>"", "followers"=>"0", "simplified_evidences_attributes"=>{"0"=>{"name"=>"qerqewrq", "description"=>"", "type"=>"url", "url"=>"qwerqwer", "sanitized"=>"false"}}} permitted: false>, "type"=>"activism", "controller"=>"foresttrees/trees", "action"=>"create", "locale"=>"en"} permitted: false>
The problem is that:
after doing that params.each {​​ |k,v| params[k] = v }​​.class
I get a hash so I loose the permit, required methods,
I tried to use slice and fetch but slice is not really working properly. And I want not loose de ActionController::Parameter Properties in my new object.
so how I can proceed
Thera are many difference between Rails 4 and Rails 5 and one is in Strong parameters:
But with Rails 5, ActionController::Parameters will no longer inherit
from HashWithIndifferentAccess
As pointed here
But if you look at the documentation, you can use other methods that can perform better your task more functionally, without the deep_dup and without using map! and work excusively in the values of the object that you want in this case.
in this case you should use transform_values also alternatively exists with !.
One thing important is that v can be a scalar (String, number ...), Array or Hash or another (ActiveController::Parameters). so you can process like this:
def forest_hash(hash)
hash.transform_values do |v|
if v.is_a?(Hash) || v.is_a?(ActionController::Parameters)
forest_hash(v)
elsif v.is_a?(String) && DATE_TIME_REGEXP === v
Time.zone.parse(v)
elsif v == ''
nil
elsif v == ['']
[]
elsif v == '<p> </p>'
nil
elsif v == 'true'
true
elsif v == 'false'
false
elsif v.is_a?(String) && v.to_i.to_s == v
v.to_i
elsif v.is_a?(Array)
v.delete_if { |x| x == '' }.map do |vv|
vv.is_a?(Hash) || vv.is_a?(ActionController::Parameters) ? forest_hash(vv) : vv
end
elsif v.is_a?(String) && base_helpers.strip_tags(v) != v
Foresttrees::TagSanitizer.new(v).sanitize
elsif v.is_a?(String)
v.squish
else
v
end
end
end
You should also, maybe encapsulate some value manipulation the scalar String and Array (hash ActiveController::Parameters always recursive) in a separate method, to made this code more ruby like and no need of the infinite if else chain.
Whith this code you return a new ActionController::Parameters that applies the sanitazions or modifications on the values

Question on how to filter x || y and not x && y

I am having trouble using || ("or").
This is the first time I select using the "or" feature and I have been trying to select the words that are greater than 6 characters long OR start with an "e". I tried everything but I keep getting just one feature or an "and". This is the code so far
def strange_words(words)
selected_words = []
i = 0
while i < words.length
word = words[i]
if word.length < 6
selected_words << word
end
i += 1
end
return selected_words
end
print strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
puts
print strange_words(["keep", "coding"])
Using the || operator is the same as writing multiple if statements. Let's use a silly example to demonstrate it. Say you wanted to determine if a word started with the letter 'e'. Well there are a few forms of 'e'. There is the lowercase e and the upppercase E. You want to check for both forms so you could do something like this:
def starts_with_e?(string)
result = false
if string[0] == 'e'
result = true
end
if string[0] == 'E'
result = true
end
result
end
Notice however that you're doing the same actions after checking for the condition. This means you could simplify this code using the OR/|| operator, like such:
def starts_with_e?(string)
result = false
if string[0] == 'e' || string[0] == 'E'
result = true
end
end
For your specific question, you can do the following:
def strange_words(words)
words.select { |word| word.length < 6 || word[0] == 'e' }
end
When you run with your example, it gives you this output:
> strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
=> ["taco", "eggs", "we", "eatihhg", "for"]
This is still not good code. You'll want to protect the methods from bad input.

Ruby - How do I shorten my method

I have a hash here:
VALID_CHOICES = {
'r' => 'rock',
'p' => 'paper',
'sc' => 'scissors',
'l' => 'lizard',
'sp' => 'spock'
}
And a method which basically compares here:
def win?(first, second)
(first == 'sc' && second == 'p') ||
(first == 'p' && second == 'r') ||
(first == 'r' && second == 'l') ||
(first == 'l' && second == 'sp') ||
(first == 'sp' && second == 'sc') ||
(first == 'sc' && second == 'l') ||
(first == 'l' && second == 'p') ||
(first == 'p' && second == 'sp') ||
(first == 'sp' && second == 'r') ||
(first == 'r' && second == 'sc')
end
How can I rewrite my method in very short concise code that means exactly the same thing? Any idea? Is it possible to do it using hashes?
You should define clear rules for what each token can win:
WINS = {
'r' => %w{l sc},
'p' => %w{r sp},
'sc' => %w{p l},
'l' => %w{p sp},
'sp' => %w{r sc}
}
Now you can determine wins using a simple lookup:
def win?(first, second)
WINS[first].include?(second)
end
While there may be several 'clever' ways to avoid an explicit structure like WINS, explicit rules are much more understandable - and therefore, more maintainable. Conciseness in code is considered a positive attribute where it improves the readability of the code. Conciseness to the extreme that causes the code to be difficult to understand is not something to strive for.
In addition to user2864740's comment and Cary Swoveland's explanation, you could also use a hash to map "winning pairs" to their respective verb:
WINS = {
%w[scissors paper] => 'cuts',
%w[paper rock] => 'covers',
%w[rock lizard] => 'crushes',
%w[lizard spock] => 'poisons',
%w[spock scissors] => 'smashes',
%w[scissors lizard] => 'decapitates',
%w[lizard paper] => 'eats',
%w[paper spock] => 'disproves',
%w[spock rock] => 'vaporizes',
%w[rock scissors] => 'crushes'
}
It returns the corresponding verb if the key's first item beats the second:
WINS[['paper', 'rock']] #=> "covers"
and nil if it doesn't:
WINS[['rock', 'paper']] #=> nil
In your method:
def win?(first, second)
WINS.has_key?([first, second])
end
Or to check both sides:
if WINS.has_key?([first, second])
# first wins
elsif WINS.has_key?([second, first])
# second wins
else
# tie
end
Or more verbose:
def result(first, second)
if verb = WINS[[first, second]]
"first wins: #{first} #{verb} #{second}"
elsif verb = WINS[[second, first]]
"second wins: #{second} #{verb} #{first}"
else
"tie"
end
end
result('rock', 'scissors')
#=> "first wins: rock crushes scissors"
result('spock', 'lizard')
#=> "second wins: lizard poisons spock"
result('paper', 'paper')
#=> "tie"
Of course, you can also use your abbreviations (sc, p, r, l, sp) instead of whole words.

Can I reuse conditions in nested if statements?

Is it possible to reuse a condition from a parent if statement?
Example:
if a == b || a == c
if a == b
#do thing
elsif a == c
#do the other thing
end
#in addition to this thing
end
Can the initial a == b or a == c be referenced in the nested statements without manually retyping them?
As pointed in the comment in ruby, the process of storing a variable inside returns the value of the variable so you can do this:
a = 3
b = 4
c = 3
if cond1 = a == b || cond2 = a == c then
if cond1 then
puts "a==b"
elsif cond2
puts "a==c"
end
puts "do this"
end
the result
irb(main):082:0> a==b
do this
=> true
i
I suggest the following.
case a
when b
...
common_code
when c
...
common_code
end
def common_code
...
end
Perhaps you can use a flag.
if a == b
flag = true
# do thing
elsif a == c
flag = true
# do the other thing
else
flag = false
end
if flag
# in addition to this thing
end
or
flag =
case a
when b
# do thing
true
when c
# do the other thing
true
else
false
end
if flag
# in addition to this thing
end

How do I count vowels?

I've seen the solution and it more or less matches
Write a method that takes a string and returns the number of vowels
in the string. You may assume that all the letters are lower cased. You can treat "y" as a consonant.
Difficulty: easy.
def count_vowels(string)
vowel = 0
i = 0
while i < string.length
if (string[i]=="a" || string[i]=="e" || string[i]=="i" || string[i]=="o"|| string[i]=="u")
vowel +=1
end
i +=1
return vowel
end
puts("count_vowels(\"abcd\") == 1: #{count_vowels("abcd") == 1}")
puts("count_vowels(\"color\") == 2: #{count_vowels("color") == 2}")
puts("count_vowels(\"colour\") == 3: #{count_vowels("colour") == 3}")
puts("count_vowels(\"cecilia\") == 4: #{count_vowels("cecilia") == 4}")
def count_vowels(str)
str.scan(/[aeoui]/).count
end
/[aeoui]/ is a regular expression that basically means "Any of these characters: a, e, o, u, i". The String#scan method returns all matches of a regular expression in the string.
def count_vowels(str)
str.count("aeoui")
end
Your function is fine you are just missing a keyword end to close of your while loop
def count_vowels(string)
vowel = 0
i = 0
while i < string.length
if (string[i]=="a" || string[i]=="e" || string[i]=="i" || string[i]=="o"|| string[i]=="u")
vowel +=1
end
i +=1
end
return vowel
end
puts("count_vowels(\"abcd\") == 1: #{count_vowels("abcd") == 1}")
puts("count_vowels(\"color\") == 2: #{count_vowels("color") == 2}")
puts("count_vowels(\"colour\") == 3: #{count_vowels("colour") == 3}")
puts("count_vowels(\"cecilia\") == 4: #{count_vowels("cecilia") == 4}")
#=> count_vowels("abcd") == 1: true
#=> count_vowels("color") == 2: true
#=> count_vowels("colour") == 3: true
#=> count_vowels("cecilia") == 4: true
I think using HashTable data structure would be good way to go for this particular problem. Especially if you're required to output number of every single vowel separately.
Here is the code I'd use:
def vowels(string)
found_vowels = Hash.new(0)
string.split("").each do |char|
case char.downcase
when 'a'
found_vowels['a']+=1
when 'e'
found_vowels['e']+=1
when 'i'
found_vowels['i']+=1
when 'o'
found_vowels['o']+=1
when 'u'
found_vowels['u']+=1
end
end
found_vowels
end
p vowels("aeiou")
Or even this (elegant but not necessarily performant):
def elegant_vowels(string)
found_vowels = Hash.new(0)
string.split("").each do |char|
case char.downcase
when ->(n) { ['a','e','i','o','u'].include?(n) }
found_vowels[char]+=1
end
end
found_vowels
end
p elegant_vowels("aeiou")
which would output:
{"a"=>1, "e"=>1, "i"=>1, "o"=>1, "u"=>1}
So you don't have to turn the string into an array and worry about case sensitivity:
def getVowelCount(string)
string.downcase.count 'aeiou'
end

Resources