I have a step that can be like this:
Then I 'eat' all food except
| Bread |
Then I 'drink' all food except
| Bread |
| Milk |
Then I 'eat' all food
I decided to put some of the arguments into the table, because otherwise it may be hard to read it.
Step definition in Ruby is:
Then(/^I '(eat|drink)' all food(?: except)?$/) do |action, exceptions|
exceptions = exceptions.raw.flatten.map(&:strip) unless exceptions.nil?
action == 'eat' ? method1(exceptions: exceptions) : method2(exceptions: exceptions)
It works fine, but not for the case when I don't pass the table argument ("Then I 'eat' all food").
Is it possible to make a table argument optional?
I'm not sure if it will work in cucumber, but as it's just a ruby block you can try to use param with splat. A single param will hold the whole parameter list then:
Then(/^I '(eat|drink)' all food(?: except)?$/) do |*params|
# params is an array
action = params[0]
exceptions = params[1] # may be nil
#...
end
More on splats
Related
country =
["UK", "US", "RS", "EU", "UK", "US"].
group_by{ |e| e }.
keep_if{ |_, e | e.length > 1 }
#⇒ {"UK"=>["UK", "UK"], "US"=>["US", "US"]}
What is the use of _ (underscore) in the second block. Can someone explain in details?
There are three answers to this on two levels.
The first answer is on the level of the Ruby language: there is no use of the underscore in Ruby. It has no meaning. It is just a legal identifier like any other legal identifier. It could have been named foo or bar instead, and it would not in any way change the meaning of this code.
The second answer is on the level of the Ruby community: in the Ruby community, the underscore is used to communicate to other programmers reading the code "I need to put an identifier here because the rules of the Ruby language force me to, but I am not actually interested in it, I will in fact never use it." So, it has no meaning to Ruby, but it does have meaning to Ruby programmers. It's just like methods ending in ! or ?: they have no special meaning, they are just used to communicate intent from one Ruby programmer to another.
The third answer is again on the level of the Ruby language: because of #2, there were two changes made in Ruby 1.9+ that codify the uses of the underscore as an "ignore me" identifier:
Normally, unused local variables generate warnings. Unused local variables that start with an underscore, or are simply named _ do not generate warnings.
Normally, an identifier can only be used once in a parameter list. Identifiers that start with an underscore, or are simply named _ can be used multiple times.
See:
#!/usr/bin/ruby -w
foo = nil
bar = nil
baz = nil
Running:
./test.rb
./test.rb:3: warning: assigned but unused variable - foo
./test.rb:4: warning: assigned but unused variable - bar
./test.rb:5: warning: assigned but unused variable - baz
But:
#!/usr/bin/ruby -w
_foo = nil
_bar = nil
_ = nil
Running:
./test.rb
And:
#!/usr/bin/ruby -w
def foo(bar, bar) end
Running:
./test.rb
./test.rb:3: duplicated argument name
def foo(bar, bar); end
[The second bar is underlined in the console, which is hard to reproduce here.]
But:
#!/usr/bin/ruby -w
def foo(_bar, _bar) end
Running:
./test.rb
So, the only difference between an underscore and no underscore is that the underscore turns off certain errors and warnings, in line with its common usage within the Ruby community.
This convention is generally also respected by linters, static analyzers, editors, and IDEs providing semantic analysis of Ruby code, e.g. IDEs that highlight and warn about unused variables will not do so for variables whose name begins with or is _.
By convention, underscore is used as a variable name for a value that is not used. Unlike other variable names, it can be used multiple times in a single parallel assignment.
In this particular case, the filter in the block is not interested in the key of the hash, but only the value of the hash, which is an array generated by group_by.
In IRB/Pry console this variable is used as last returned value. There could be interesting cases beyond just being a placeholder, like:
1
_ + 2
_ * 3
_.to_f / 2
which returns:
1
=> 1
_ + 2
=> 3
_ * 3
=> 9
_.to_f / 2
=> 4.5
or
[:flowers]
_ << :leaves
which results in:
[:flowers]
=> [:flowers]
_ << :leaves
=> [:flowers, :leaves]
You can use it for variables/classes/methods basically _ contains the result of previous line you types, this is very convenient when called some LongRunning.method while in console and forgot to assign to a variable like result = ... for later inspection, so you can just type result = _ or simply _ and assign your data to the variable.
I am reading this code from RSpec:
require 'rspec/expectations'
RSpec::Matchers.define :be_a_multiple_of do |expected|
match do |actual|
actual % expected == 0
end
end
RSpec.describe 9 do
it { is_expected.to be_a_multiple_of(3) }
end
What is the match do block? It's a block inside a proc? The syntax is a bit odd... is the define method taking two arguments; the first is a symbol and the second is a Proc?
Under the hood... how does actual refer to the integer 9?
expect(object).to eq(value)
| | |
| | expected (matcher argument)
| |
actual |
matcher
What's happening in this code?
RSpec::Matchers.define :be_a_multiple_of do |expected|
When RSpec::Matcher.define is called, it is passed the name of a matcher ( which in this case is be_a_multiple_of ) and a block
The argument passed to that block is the expected value. This is the value passed as the matcher argument when running a spec, which would be 3 in this case be_a_multiple_of(3)
match do |actual|
Inisde that block, the match method is called which is also passed a block. That block takes one argument, actual, which is the value passed to expect when the matcher is invoked. In this case that would be 9, expect(9).to be_multipe_of(3)
The block just does some calculation using actual and expected to return true or false.
How does actual refer to 9?
RSpec.describe 9 do
In this case the value 9 is exposed as the subject for the example group because it is passed as the argument to the outermost example group as stated here In other words it's the same as writing subject { 9 }
If you check the RSpec doc is_expected is basically the same as writing expect(subject).
{ is_expected.to be_a_multiple_of(3) }
is the same as
{ expect(subject).to be_a_multiple_of(3) }
And as stated earlier the object passed to expect method is what is used as the actual argument for the block passed to the match method. That's how actual refers to 9
I'm working on some Watir-webdriver tests in Ruby and can't seem to get the following code to work. I want to pass an optional validation argument into the log_activity method.
def log_activity (type, *validation)
#do something
end
I pass the following arguments into the method:
log_activity("license", 1)
I expect validation == 1 to be true, but it is false:
puts validation.empty?
-> false
puts validation
-> 1
if validation == 1
puts "validation!!!!"
else
puts "WTF"
end
-> WTF
What am I doing wrong?
Forgot to mention, I'm using ruby 1.9.3
*validation is an array that includes the second and all arguments afterwards. Given that it is an array, the results you see make sense. You want to check the first element in the *validation array.
Alternatively, if you will only get one optional argument, you can do:
def log_activity (type, validation=nil)
#do something
end
Then validation will be whatever you passed in.
Read "Method Arguments In Ruby" and look at "Optional Arguments". I found it pretty handy.
I am pasting the useful content:
Optional Arguments
If you want to decide at runtime how many – if any – arguments you will supply to a method, Ruby allows you to do so. You need to use a special notation when you define the method, e.g.:
def some_method(*p)
end
You can call the above method with any number of arguments (including none), e.g.:
some_method
or
some_method(25)
or
some_method(25,"hello", 45, 67)
All of those will work. If no arguments are supplied, then p will be an empty array, otherwise, it will be an array that contains the values of all the arguments that were passed in.
when you use *args as the last argument in Ruby, args is an array.
Unfortunately for you, on Ruby 1.8, array.to_s == array.join("")
Try either
if validation == [1]
or
if validation.first == 1
Can someone explain to me Ruby's use of pipe characters in a block? I understand that it contains a variable name that will be assigned the data as it iterates. But what is this called? Can there be more than one variable inside the pipes? Anything else I should know about it? Any good links to more information on it?
For example:
25.times { | i | puts i }
Braces define an anonymous function, called a block. Tokens between the pipe are the arguments of this block. The number of arguments required depends on how the block is used. Each time the block is evaluated, the method requiring the block will pass a value based on the object calling it.
It's the same as defining a method, only it's not stored beyond the method that accepts a block.
For example:
def my_print(i)
puts i
end
will do the same as this when executed:
{|i| puts i}
the only difference is the block is defined on the fly and not stored.
Example 2:
The following statements are equivalent
25.times &method(:my_print)
25.times {|i| puts i}
We use anonymous blocks because the majority of functions passed as a block are usually specific to your situation and not worth defining for reuse.
So what happens when a method accepts a block? That depends on the method. Methods that accept a block will call it by passing values from their calling object in a well defined manner. What's returned depends on the method requiring the block.
For example: In 25.times {|i| puts i} .times calls the block once for each value between 0 and the value of its caller, passing the value into the block as the temporary variable i. Times returns the value of the calling object. In this case 25.
Let's look at method that accepts a block with two arguments.
{:key1 => "value1", :key2 => "value2"}.each {|key,value|
puts "This key is: #{key}. Its value is #{value}"
}
In this case each calls the block ones for each key/value pair passing the key as the first argument and the value as the second argument.
The pipes specify arguments that are populated with values by the function that calls your block. There can be zero or more of them, and how many you should use depends on the method you call.
For example, each_with_index uses two variables and puts the element in one of them and the index in the other.
here is a good description of how blocks and iterators work
Block arguments follow all the same conventions as method parameters (at least as of 1.9): you can define optional arguments, variable length arg lists, defaults, etc. Here's a pretty decent summary.
Some things to be aware of: because blocks see variables in the scope they were defined it, if you pass in an argument with the same name as an existing variable, it will "shadow" it - your block will see the passed in value and the original variable will be unchanged.
i = 10
25.times { | i | puts i }
puts i #=> prints '10'
Will print '10' at the end. Because sometimes this is desirable behavior even if you are not passing in a value (ie you want to make sure you don't accidentally clobber a variable from surrounding scope) you can specify block-local variable names after a semicolon after the argument list:
x = 'foo'
25.times { | i ; x | puts i; x = 'bar' }
puts x #=> prints 'foo'
Here, 'x' is local to the block, even though no value is passed in.
I have a method that can return either a single object or a collection of objects. I want to be able to run object.collect on the result of that method whether or not it is a single object or a collection already. How can i do this?
profiles = ProfileResource.search(params)
output = profiles.collect do | profile |
profile.to_hash
end
If profiles is a single object, I get a NoMethodError exception when I try to execute collect on that object.
Careful with the flatten approach, if search() returned nested arrays then unexpected behaviour might result.
profiles = ProfileResource.search(params)
profiles = [profiles] if !profiles.respond_to?(:collect)
output = profiles.collect do |profile|
profile.to_hash
end
Here's a one Liner:
[*ProfileResource.search(params)].collect { |profile| profile.to_hash }
The trick is the splat (*) that turns both individual elements and enumerables into arguments lists (in this case to the new array operator)
profiles = [ProfileResource.search(params)].flatten
output = profiles.collect do |profile|
profile.to_hash
end
In the search method of the ProfileResource class, always return a collection of objects (usually an Array), even if it contains only one object.
If the collection is an Array you could use this technique
profiles = [*ProfileResource.search(params)]
output = profiles.collect do | profile |
profile.to_hash
end
That would guaranteed your profiles is always an array.
profiles = ProfileResource.search(params)
output = Array(profiles).collect do |profile|
profile.to_hash
end
You could first check to see if the object responds to the "collect" method by using "pofiles.respond_to?".
From Programming Ruby
obj.respond_to?(
aSymbol, includePriv=false ) -> true
or false
Returns true if obj responds to the
given method. Private methods are
included in the search only if the
optional second parameter evaluates to
true.
You can use the Kernel#Array method as well.
profiles = Array(ProfileResource.search(params))
output = profiles.collect do | profile |
profile.to_hash
end
Another way is to realise that Enumerable requires that you supply an each method.
So. you COULD mix in Enumerable to your class and give it a dummy each that works....
class YourClass
include Enumerable
... really important and earth shattering stuff ...
def each
yield(self) if block_given?
end
end
This way, if you get back a single item on its own from the search, the enumerable methods will still work as expected.
This way has the advantage that all the support for it is inside your class, not outside where it has to be duplicated many many times.
Of course, the better way is to change the implementation of search such that it returns an array irrespective of how many items is being returned.