I am working on some refactoring tool. It would be great if I can replace some method call in place by its definition, which would generally be a block of statements. For example, the original code may be:
some_condition ? a : b
def a
...
# statements1
...
end
def d
...
# statements2
...
end
and I want my inlining tool to replace the method call by the blocks of code directly.
To do that, I want to group a list of statements together. How do we usually do that? Is there a way of writing code like this?
some_condition ? {
...
# statements1
...
} : {
...
# statements2
...
}
Yes. You can either use parentheses, or begin...end.
true ? (
puts "a"
puts "b"
puts "c"
) : (
puts "d"
)
true ? begin
puts "a"
puts "b"
puts "c"
end : begin
puts "d"
end
Your code can be reduced to just one line.
%w(a b c).each { |char| puts char }
The else statement is irrelevant here, because true will always return true.
Related
I have the following situation:
type = "stringX"
someArray = ["stringX", "string1", "string2"]
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when someArray.any? { |x| x.include?(type) }
puts "x"
when "stringC"
puts "c"
end
What I was expecting to happen was that it would go through the case and once it evaluates the .any? method as true (because by itself it does evaluate to true), it would puts "x". However, that's not what's happening here, it just goes through the rest of the case and reaches a raise somewhere below that.
I'm wondering what's going on here?
Use * operator
value = "stringX"
some_array = ["stringX", "string1", "string2"]
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when *some_array # notice the * before the variable name!
puts "x"
when "stringC"
puts "c"
end
How does this work?
when *some_array checks whether value is an element in some_array
For this particular case one should use the brilliant answer by #akuhn
Whether you need to put any random condition inside the case, you can do it using Proc#===:
type = "stringX"
someArray = ["stringX", "string1", "string2"]
case type
when "stringA" then puts "a"
when "stringB" then puts "b"
# ⇓⇓⇓⇓⇓⇓⇓⇓ HERE
when ->(type) { someArray.any? { |x| x.include?(type) } }
puts "x"
when "stringC" then puts "c"
end
EDIT: I will not delete the answer, because I think there might be something in it you didn't know before, but it does not work for your usecase. For that you should look at mudasobwas answer
It does not quite work this way, because basically the case statement will compare the given object with the object(s) passed to when, about similar to this:
if type == "stringA"
# ...
elsif type == "stringB"
# ...
and so on, unless you use an empty case statement.
case
when type == "stringA"
# ...
This is similar to an if elsif statement though, so you don't really see that very often.
In your case however, we can make use of Ruby's splat operator
case type
when "stringA"
puts "a"
when "stringB"
puts "b"
when *someArray
puts "x"
when "stringC"
puts "c"
Ruby's case statement can take multiple arguments with when which kind of works like an "or"
case "A"
when "B"
puts "B"
when "C", "A"
puts "C or A"
end
# => C or A
and the splat operator will fan out your array:
p ["a", "b"]
# => ["a", "b"]
p *["a", "b"]
# => "a"
# => "b"
p "a", "b"
# => "a"
# => "b"
I have:
foos.each do |foo|
unless foo
puts "Foo is missing"
next
end
# rest of business logic goes here
end
I would like to write the last part of it better, something like
{ puts "Foo is missing"; next } unless foo
Unfortunately, this does not work. Does anybody know a way to write two (blocks of) commands inline with if condition?
Just use parentheses:
(puts 'a'; puts 'b') if true
#=> a
#=> b
What you are looking for can be done with parentheses:
(puts "Foo is missing"; next) unless foo
But in this particular case, it is better to write:
next puts "Foo is missing" unless foo
Use begin..end block:
begin puts "Foo is missing"; next end unless foo
foos.each { |foo| foo or ( puts "Foo is missing"; next )
# the rest of the business logic goes here
}
You can use the or syntax
[1,2,3].each do |x|
puts 'two' or next if x == 2
puts x
end
#=> 1
#=> "two"
#=> 3
I am trying to make this code return when called without a block. The uncommented lines at the bottom is what I'm trying to get to return. The first uncommented line should return in tut, second line converted to english and the last should be in english. And why is the line " puts eng " returning up and down and not in sentence form? Thanks for any and all help.
Here's my code:
class Tut
##consonants = ["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"]
def is_tut? string
if string =~ /^(([b-df-hj-np-z]ut)|([aeiou\s])|[[:punct:]])+$/i
yield
else
false
end
end
def self.to_tut string
string.each_char do |c|
c += "ut" if ##consonants.find { |i| i == c.downcase }
yield c if block_given?
end
end
def self.to_english string
array = string.split //
array.each do |c|
if ##consonants.find { |i| i == c.downcase }
array.shift
array.shift
end
yield c if block_given?
end
end
end
#Tut.to_tut( "Wow! Look at this get converted to Tut!" ) { |c| print c }
# should output : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
#puts
#puts
tut = Tut.to_tut( "Wow! Look at this get converted to Tut!" )
puts "from return: #{tut}"
puts
#Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" ) { |c| print c }
#should outout : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
#puts
#puts
tut = Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" )
puts "from return: #{tut}"
#puts
#tut_string = ""
#Tut.to_tut( "I'm in tut but I want to be in english." ) { |c| tut_string += c }
#puts tut_string
# should output : I'mut inut tututut bututut I wutanuttut tuto bute inut enutgutlutisuthut.
puts
#Tut.to_english( tut_string ){ |c| print c }
# should output : I'm in tut but I want to be in english.
lan = Tut.to_english( tut )
puts lan
(Opening note: You normally don't want to modify an Enumerable object while iterating over it, since that makes it much harder to read the code and debug it.)
Your to_tut doesn't retain your modifications because the "c" block variable is a copy of the string slice, instead of a reference to part of the string (if it were a ref, you'd be able to use << to append; "+=" still wouldn't work because it reassigns rather than changing the ref). That's just how each_char works, since a String doesn't contain references.
If you wanted to modify the string in place, you'd probably have to count backwards and then insert the 'ut' by index via string#[]= . But that's way complicated so I'll present a couple alternates.
Working to_tut #1:
def self.to_tut string
string.chars.map do |c|
yield c if block_given?
# this must be the last expression the block
if ##consonants.find { |i| i == c.downcase }
c + 'ut'
else
c
end
end.join
end
Working to_tut #2 - this is probably the most ruby-ish way to do it:
def self.to_tut string
string.gsub(/[#{##consonants.join}]/i) {|match|
yield match if block_given?
# this must be the last expression in the block
match + 'ut'
}
end
Your to_english doesn't work because array.shift always removes the first element of the array. Instead, you want to track the current index, and remove 2 chars starting from index+1.
Working to_english:
def self.to_english2 string
array = string.split //
array.each_with_index do |c, idx|
if ##consonants.find { |i| i == c.downcase }
array.slice!(idx+1, 2)
end
yield c if block_given?
end
array.join
end
Regarding why your "puts lan" returns one char per line - it's because your to_english returns an array. You'll want to call join to convert it.
The methods to_tut and to_english are giving you wrong answers when used without a block. This happens because ruby always returns the last value evaluated in your method. In your code that will be the result of the string.each_char for to_tut or array.each for to_english. In both cases, the result contains the original input, which is consequently returned and printed.
As to the puts eng, it prints the array returned by array.each of to_english.
I keep getting a 'no block given' error when trying to pass the string to the is_tut? method. I am new to Ruby and have no idea what I'm doing wrong. Any and all help would be appreciated.
class Tut
##consonants = ["b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z"]
def is_tut? string
if string =~ /^(([b-df-hj-np-z]ut)|([aeiou\s])|[[:punct:]])+$/i
yield
else
false
end
end
def self.to_tut string
string.each_char do |c|
c += "ut" if ##consonants.find { |i| i == c.downcase }
yield c
end
end
def self.to_english string
array = string.split //
array.each do |c|
if ##consonants.find { |i| i == c.downcase }
array.shift
array.shift
end
yield c
end
end
end
#Tut.to_tut( "Wow! Look at this get converted to Tut!" ) { |c| print c }
# should output : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
puts
puts
tut = Tut.to_tut( "Wow! Look at this get converted to Tut!" )
puts "from return: #{tut}"
puts
#Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" ) { |c| print c }
# should outout : Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!
puts
puts
#tut = Tut.to_tut( "Wutowut! Lutookut atut tuthutisut gutetut cutonutvuteruttutedut tuto Tututut!" )
#puts "from return: #{tut}"
puts
#tut_string = ""
#Tut.to_tut( "I'm in tut but I want to be in english." ) { |c| tut_string += c }
#puts tut_string
# should output : I'mut inut tututut bututut I wutanuttut tuto bute inut enutgutlutisuthut.
puts
#Tut.to_english( tut_string ) { |c| print c }
# should output : I'm in tut but I want to be in english.
If you have yield within your method definition, that means you have to obligatorily pass a block when you use it (unless the part including it is not executed according to conditioning etc.). (You might already know, but in case you don't: a block is something that is described as {...} or do ... end) And yield will refer to the block.
If you want to make a block optional, then one way to do it is to put the & symbol before the variable name.
def method(argument, &block_argument)
if block_argument # block is given
block_argument.call(argument_for_block) # use call to execute the block
else # the value of block_argument becomes nil if you didn't give a block
# block was not given
end
end
This will allow optional block. Or, as suggested by Squeegy,
def method(argument)
if block_given? # block is given
yield(argument_for_block) # no need to use call to execute the block
else
# block was not given
end
end
will also work.
Because you're calling yield in your to_tut() method, this line will fail:
tut = Tut.to_tut( "Wow! Look at this get converted to Tut!" )
You either need to give a block (as you did in the first commented-out call to Tut.to_tut()), or you need to modify your to_tut() function to make the code block optional:
def self.to_tut string
string.each_char do |c|
c += "ut" if ##consonants.find { |i| i == c.downcase }
yield c if block_given?
end
end
yield requires a block to be passed to to_tut.
When you do:
Tut.to_tut( "Wow! Look at this get converted to Tut!" ) { |c| print c }
It works because it has the block { |c| print c }.
Without a block it will raise the error.
Is there a good way to chain methods conditionally in Ruby?
What I want to do functionally is
if a && b && c
my_object.some_method_because_of_a.some_method_because_of_b.some_method_because_of_c
elsif a && b && !c
my_object.some_method_because_of_a.some_method_because_of_b
elsif a && !b && c
my_object.some_method_because_of_a.some_method_because_of_c
etc...
So depending on a number of conditions I want to work out what methods to call in the method chain.
So far my best attempt to do this in a "good way" is to conditionally build the string of methods, and use eval, but surely there is a better, more ruby, way?
You could put your methods into an array and then execute everything in this array
l= []
l << :method_a if a
l << :method_b if b
l << :method_c if c
l.inject(object) { |obj, method| obj.send(method) }
Object#send executes the method with the given name. Enumerable#inject iterates over the array, while giving the block the last returned value and the current array item.
If you want your method to take arguments you could also do it this way
l= []
l << [:method_a, arg_a1, arg_a2] if a
l << [:method_b, arg_b1] if b
l << [:method_c, arg_c1, arg_c2, arg_c3] if c
l.inject(object) { |obj, method_and_args| obj.send(*method_and_args) }
You can use tap:
my_object.tap{|o|o.method_a if a}.tap{|o|o.method_b if b}.tap{|o|o.method_c if c}
Sample class to demonstrate chaining methods that return a copied instance without modifying the caller.
This might be a lib required by your app.
class Foo
attr_accessor :field
def initialize
#field=[]
end
def dup
# Note: objects in #field aren't dup'ed!
super.tap{|e| e.field=e.field.dup }
end
def a
dup.tap{|e| e.field << :a }
end
def b
dup.tap{|e| e.field << :b }
end
def c
dup.tap{|e| e.field << :c }
end
end
monkeypatch: this is what you want to add to your app to enable conditional chaining
class Object
# passes self to block and returns result of block.
# More cumbersome to call than #chain_if, but useful if you want to put
# complex conditions in the block, or call a different method when your cond is false.
def chain_block(&block)
yield self
end
# passes self to block
# bool:
# if false, returns caller without executing block.
# if true, return result of block.
# Useful if your condition is simple, and you want to merely pass along the previous caller in the chain if false.
def chain_if(bool, &block)
bool ? yield(self) : self
end
end
Sample usage
# sample usage: chain_block
>> cond_a, cond_b, cond_c = true, false, true
>> f.chain_block{|e| cond_a ? e.a : e }.chain_block{|e| cond_b ? e.b : e }.chain_block{|e| cond_c ? e.c : e }
=> #<Foo:0x007fe71027ab60 #field=[:a, :c]>
# sample usage: chain_if
>> cond_a, cond_b, cond_c = false, true, false
>> f.chain_if(cond_a, &:a).chain_if(cond_b, &:b).chain_if(cond_c, &:c)
=> #<Foo:0x007fe7106a7e90 #field=[:b]>
# The chain_if call can also allow args
>> obj.chain_if(cond) {|e| e.argified_method(args) }
Although the inject method is perfectly valid, that kind of Enumerable use does confuse people and suffers from the limitation of not being able to pass arbitrary parameters.
A pattern like this may be better for this application:
object = my_object
if (a)
object = object.method_a(:arg_a)
end
if (b)
object = object.method_b
end
if (c)
object = object.method_c('arg_c1', 'arg_c2')
end
I've found this to be useful when using named scopes. For instance:
scope = Person
if (params[:filter_by_age])
scope = scope.in_age_group(params[:filter_by_age])
end
if (params[:country])
scope = scope.in_country(params[:country])
end
# Usually a will_paginate-type call is made here, too
#people = scope.all
Use #yield_self or, since Ruby 2.6, #then!
my_object.
then{ |o| a ? o.some_method_because_of_a : o }.
then{ |o| b ? o.some_method_because_of_b : o }.
then{ |o| c ? o.some_method_because_of_c : o }
Here's a more functional programming way.
Use break in order to get tap() to return the result. (tap is in only in rails as is mentioned in the other answer)
'hey'.tap{ |x| x + " what's" if true }
.tap{ |x| x + "noooooo" if false }
.tap{ |x| x + ' up' if true }
# => "hey"
'hey'.tap{ |x| break x + " what's" if true }
.tap{ |x| break x + "noooooo" if false }
.tap{ |x| break x + ' up' if true }
# => "hey what's up"
Maybe your situation is more complicated than this, but why not:
my_object.method_a if a
my_object.method_b if b
my_object.method_c if c
I use this pattern:
class A
def some_method_because_of_a
...
return self
end
def some_method_because_of_b
...
return self
end
end
a = A.new
a.some_method_because_of_a().some_method_because_of_b()
If you're using Rails, you can use #try. Instead of
foo ? (foo.bar ? foo.bar.baz : nil) : nil
write:
foo.try(:bar).try(:baz)
or, with arguments:
foo.try(:bar, arg: 3).try(:baz)
Not defined in vanilla ruby, but it isn't a lot of code.
What I wouldn't give for CoffeeScript's ?. operator.
I ended up writing the following:
class Object
# A naïve Either implementation.
# Allows for chainable conditions.
# (a -> Bool), Symbol, Symbol, ...Any -> Any
def either(pred, left, right, *args)
cond = case pred
when Symbol
self.send(pred)
when Proc
pred.call
else
pred
end
if cond
self.send right, *args
else
self.send left
end
end
# The up-coming identity method...
def itself
self
end
end
a = []
# => []
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(:empty?, :itself, :push, 1)
# => [1]
a.either(true, :itself, :push, 2)
# => [1, 2]