Match an argument's attribute value in rspec - ruby

Is there a way to match an argument's attribute value in rspec? Checking the documentation it looks like there's other matchers available, such as anything, an_instance_of, and hash_including - but nothing to check the value of an object's attribute.
Example - suppose I have this instance method:
class DateParser
def parse_year(a_date)
puts a_date.year
end
end
Then I can write an expectation like this:
dp = DateParser.new
expect(dp).to receive(:parse_year).with(some_matcher)
What I want for some_matcher to check that parse_year is called with any object that has an attribute year that has the value 2014. Is this possible with the out-of-the-box argument matchers in rspec, or does a custom one have to be written?

You can pass a block and set expectations about the argument(s) within the block:
describe DateParser do
it "expects a date with year 2014" do
expect(subject).to receive(:parse_year) do |date|
expect(date.year).to eq(2014)
end
subject.parse_year(Date.new(2014,1,1))
end
end

Maybe something using a double for the passed-in date?
date = double()
date.should_receive(:year)
DateParser.new.parse_year(date)
It's not clear what you mean by the date needing to be 2014. But you could add .and_return(2014) to it to get that behavior from the double.

Related

How to create and use variables dynamically named by string values in Ruby?

I'm using SitePrism to create some POM tests. One of my page classes looks like this:
class HomePage < SitePrism::Page
set_url '/index.html'
element :red_colour_cell, "div[id='colour-cell-red']"
element :green_colour_cell, "div[id='colour-cell-green']"
element :blue_colour_cell, "div[id='colour-cell-blue']"
def click_colour_cell(colour)
case colour
when 'red'
has_red_colour_cell?
red_colour_cell.click
when 'green'
has_green_colour_cell?
green_colour_cell.click
when 'blue'
has_blue_colour_cell?
blue_colour_cell.click
end
end
end
The method click_colour_cell() get its string value passed from a Capybara test step that calls this method.
If I need to create additional similar methods in the future, it can become rather tedious and unwieldy having so many case switches to determine the code flow.
Is there some way I can create a variable that is dynamically named by the string value of another variable? For example, I would like to do something for click_colour_cell() that resembles the following:
def click_colour_cell(colour)
has_#colour_colour_cell?
#colour_colour_cell.click
end
where #colour represents the value of the passed value, colour and would be interpreted by Ruby:
def click_colour_cell('blue')
has_blue_colour_cell?
blue_colour_cell.click
end
Isn't this what instance variables are used for? I've tried the above proposal as a solution, but I receive the ambiguous error:
syntax error, unexpected end, expecting ':'
end
^~~ (SyntaxError)
If it is an instance variable that I need to use, then I'm not sure I'm using it correctly. if it's something else I need to use, please advise.
Instance variables are used define properties of an object.
Instead you can achieve through the method send and string interpolation.
Try the below:
def click_colour_cell(colour)
send("has_#{colour}_colour_cell?")
send("#{colour}_colour_cell").click
end
About Send:
send is the method defined in the Object class (parent class for all the classes).
As the documentation says, it invokes the method identified by the given String or Symbol. You can also pass arguments to the methods you are trying to invoke.
On the below snippet, send will search for a method named testing and invokes it.
class SendTest
def testing
puts 'Hey there!'
end
end
obj = SendTest.new
obj.send("testing")
obj.send(:testing)
OUTPUT
Hey there!
Hey there!
In your case, Consider the argument passed for colour is blue,
"has_#{colour}_colour_cell?" will return the string"has_blue_colour_cell?" and send will dynamically invoke the method named has_blue_colour_cell?. Same is the case for method blue_colour_cell
Direct answer to your question
You can dynamically get/set instance vars with:
instance_variable_get("#build_string_as_you_see_fit")
instance_variable_set("#build_string_as_you_see_fit", value_for_ivar)
But...
A Warning!
I think dynamically creating variables here and/or using things like string-building method names to send are a bad idea that will greatly hinder future maintainability.
Think of it this way: any time you see method names like this:
click_blue_button
click_red_button
click_green_button
it's the same thing as doing:
add_one_to(1) // instead of 1 + 1, i.e. 1.+(1)
add_two_to(1) // instead of 1 + 2, i.e. 1.+(2)
add_three_to(1) // instead of 1 + 3, i.e. i.+(3)
Instead of passing a meaningful argument into a method, you've ended up hard-coding values into the method name! Continue this and eventually your whole codebase will have to deal with "values" that have been hard-coded into the names of methods.
A Better Way
Here's what you should do instead:
class HomePage < SitePrism::Page
set_url '/index.html'
elements :color_cells, "div[id^='colour-cell-']"
def click_cell(color)
cell = color_cells.find_by(id: "colour-cell-#{color}") # just an example, I don't know how to do element queries in site-prism
cell.click
end
end
Or if you must have them as individual elements:
class HomePage < SitePrism::Page
set_url '/index.html'
COLORS = %i[red green blue]
COLORS.each do |color|
element :"#{color}_colour_cell", "div[id='colour-cell-#{color}']"
end
def cell(color:) # every other usage should call this method instead
#cells ||= COLORS.index_with do |color|
send("#{color}_colour_cell") # do the dynamic `send` in only ONE place
end
#cells.fetch(color)
end
end
home_page.cell(color: :red).click

Custom Methods for Treetop Syntax Nodes

I have a Treetop PEG grammar that matches some keys. I want to look up the values associated with those keys in a hash I give the parser. How can I make it so that the syntax nodes have access to methods or variables from the parser?
For example, here's a simple grammar that finds a single word and tries to look up its value:
# var.treetop
grammar VarResolver
include VarLookup
rule variable
[a-zA-Z] [a-zA-Z0-9_]*
{
def value
p found:text_value
find_variable(text_value)
end
}
end
end
Here's a test file using it:
# test.rb
require 'treetop'
module VarLookup
def set_variables(variable_hash)
#vars = variable_hash
end
def find_variable(str)
#vars[str.to_sym]
end
end
Treetop.load('var.treetop')
#p = VarResolverParser.new
#p.set_variables name:'Phrogz'
p #p.parse('name').value
Running this test, I get the output:
{:found=>"name"}
(eval):16:in `value': undefined method `find_variable'
for #<Treetop::Runtime::SyntaxNode:0x00007f88e091b340> (NoMethodError)
How can I make find_variable accessible inside the value method? (In the real parser, these rules are deeply nested, and need to resolve the value without returning the actual name to the top of the parse tree. I cannot just return the text_value and look it up outside.)
This is a significant weakness in the design of Treetop.
I (as maintainer) didn't want to slow it down further by
passing yet another argument to every SyntaxNode,
and break any custom SyntaxNode classes folk have
written. These constructors get the "input" object, a Range
that selects part of that input, and optionally an array
of child SyntaxNodes. They should have received the
Parser itself instead of the input as a member.
So instead, for my own use (some years back), I made
a custom proxy for the "input" and attached my Context
to it. You might get away with doing something similar:
https://github.com/cjheath/activefacts-cql/blob/master/lib/activefacts/cql/parser.rb#L203-L249

Getting the default value of named or optional parameter

I have a method that looks like this:
class A
def my_method(a: 'key value', b = 'opt value')
# ...
end
end
Using reflection, I can get the parameter names like this:
A.new.method(:my_method).parameters
# => [[:key, :a], [:opt, :b]]
How can I get the default values of these parameters without invoking my_method?
This is not possible. I can't find the thread right now, but matz has explicitly said that this is by design. The problem is that the default value is an arbitrary expression, and since everything in Ruby is an expression, it can be anything.
For example, what about this:
def foo(bar = if rand < 0.5 then Time.now else rand end)
What would your proposed method return here?
Basically, you have two choices:
Evaluate the default value expression. This means you will get some value, but that doesn't tell you much about what the real expression is.
Don't evaluate the default value expression, by packaging it up in a Proc. But there's no way to get the expression out of a Proc again.
So, either way, you don't actually get any useful information.

What's the cleanest way to define a constant string that involves a variable in Ruby?

For some context, a lot of my code has the same lines of text throughout it (we are using Calabash to do iOS automation, if that gives you an idea).
For example: "all label marked:'#{name}'" is used 8 times in a particular class.
I would prefer to be able to have a constant that uses that text, but if I throw it at the top of the class, of course the variable "name" has not been set yet. Without defining a method that takes a parameter and returns a string, is there a way to do something essentially like this that can exist at the top of the class, but not be evaluated until it's used?:
class ClassName
extend Calabash::Cucumber::Operations
#NAME_CONSTANT = "all label marked:'#{name}'"
def self.method_name(name)
query("#{#NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0")
end
end
If you use the syntax I mentioned, you get this error: undefined local variable or method `name' for ClassName
You could use String#% to insert the string later.
class ClassName
#NAME_CONSTANT = "all label marked:'%{name}'"
def self.method_name(insert_name)
query("#{#NAME_CONSTANT} sibling label marked:'anotherLabel' isHidden:0" % {name: insert_name})
end
def self.query(string)
puts string
end
end
ClassName.method_name('test')
#=> "all label marked:'test' sibling label marked:'anotherLabel' isHidden:0"
I agree with #Sergio. Don't define a constant string that includes a variable. Just use a method. Including a variable in a constant seems like a bad idea. Constants shouldn't be dynamic, by definition.
If you really want to include a variable in a constant string, you can assign a lambda to a contstant, like so:
class ClassName
extend Calabash::Cucumber::Operations
NAME_CONSTANT = ->(name) { "all label marked:'#{name}'" }
def self.method_name(name)
query("#{NAME_CONSTANT.call(name)} sibling label marked:'anotherLabel' isHidden:0")
end
end
I removed the # before the constant, since including it creates a class-level instance variable, not a constant.
I really wouldn't use the code sample I posted, though. Just use a method. Avdi Grimm has a good post called "Do we need constants?" where he describes some of the benefits of using methods instead of constants.
The fundamental issue you're facing is that string interpolation occurs at the time the literal is interpreted and the scope of any referenced variables is determined by the location of the string in the code.
If you put the interpolated string in a method, then it won't have access to the local definition of any variables used in the string. You'd have to pass in the value of any variables used, as in:
def name_constant(name)
"all label marked:'#{name}'"
end
Alternatively, you'd need to declare the "constant" as an uninterpreted string as follows:
#name_constant = '"all label marked:''#{name}''"'
and then interpret it when you reference it, as follows:
eval(#name_constant)
BTW, I've ignored the issue of this not really being a "constant" and using instance variables vs. class variables.

Validate DateTime object in Ruby

A constructor in following code receives a DateTime object in the parameter
class Test
attr_accessor :dateTime
# Takes DateTime object as input
def initialize(dateTime)
#dateTime = dateTime
end
end
How can I validate if dateTime parameter passed to the constructor is valid DateTime object or not?
One way to find out is by using:
if dateTime.class == DateTime
But is there a better way to do it?
Remember, dateTime is DateTime object and not a string
You can check it by dateTime.is_a?(DateTime) it will return boolean
Maybe use is_a? or kind_of?, to be flexible with potential subclasses.
http://www.ruby-doc.org/core/classes/Object.html#M001033
Try using the is_a? method, this way if the object is an instance of your target class (DateTime) or a subclass then you can accept it:
dateTime.is_a?(DateTime)
If all you want is to make sure that you get an instance of DateTime (or a subclass), then the other "is is_a?" answers are what you want.
However, you should consider being a little more forgiving of your inputs. What if someone hands you a string like "2011-06-28 23:31:11"? To most people, that's a DateTime even though Ruby thinks it is an instance of String. If you want to be friendlier, you could try DateTime.parse this:
begin
dt = dt.is_a?(DateTime) ? dt : DateTime.parse(dt)
rescue ArgumentError
# Do your failure stuff here
end
Or, if you're in Rails:
begin
dt = dt.to_datetime
rescue ArgumentError, NoMethodError
# Do your failure stuff here
end
These approaches give you a lot of flexibility without losing anything in the process. Be forgiving of your input but strict in your output.
For reference, in Rails 3, the following have to_datetime methods:
Date
DateTime
Time
String

Resources