rspec check any argument is hash including particular pair - ruby

I have a shared rspec example for a number of functions (rest requests). Each function receives a hash which I want to check, but they can be at different positions, e.g.:
get(url, payload, headers)
delete(url, headers)
I want to write the following test:
shared_examples_for "any request" do
describe "sets user agent" do
it "defaults to some value" do
rest_client.should_receive(action).with(????)
run_request
end
it "to value passed to constructor"
end
end
end
describe "#create" do
let(:action) {:post}
let (:run_action) {rest_client.post(url, payload, hash_i_care_about)}
it_behaves_like "any request"
end
The question is, how can I write a matcher which matches any of the arguments, e.g.:
client.should_receive(action).with(arguments_including(hash_including(:user_agent => "my_agent")))

In order to match any argument, you can pass a block to should_receive which can then check the arguments in any fashion you want:
client.should_receive(action) do |*args|
# code to check that one of the args is the kind of hash you want
end
You could search the list of args for an argument of hash type, you could pass a parameter into the shared example indicating what position in the argument list the hash should exist in etc. Let me know if this is not clear and I can provide more detail.
This is covered briefly in https://www.relishapp.com/rspec/rspec-mocks/docs/argument-matchers

My example is a little bit in conjunction with what Peter Alfvin suggested:
shared_examples "any_request" do |**args|
action = args[:action]
url = args[:url]
payload = args[:payload]
headers = args[:headers]
# ...etc
case action
when :get
# code to carry on
when :post
# code to continue
end
end
This way you can define and work with the arguments as your code expands, in any order and in any amount. You would call the function like so:
it_behaves_like "any_request", { action: :post,
url: '/somewhere' }
Undeclared arguments, like :payload in this example would automatically carry the value of nil. Test its existence like: if payload.nil? or unless payload.nil? etc.
Note: This works for Ruby 2.0, Rails 4.0, rspec 2.13.1. Actual code definitions may vary on earlier versions.
Note_note: ... do |**args| the two asterisks is not a typo ;)

Related

Ruby method with optional options and &block parameter

Hey there
Is it possible to have optional attributes and a block as parameters
for a method call?
Example: I have to call
method(foo, foo: bar, -> { do_something }
and tried it with
def method(foo, *bar, &block)
end
As for my understanding the block always has to be at last position?
After a bit of research I found out the unary(?) * seems to be for
arrays. Since I try to pass a Hash I changed the code to
def method(foo, bar={}, &block)
end
But this doesn't do the trick either. I guess its because he cant
figure out where the bar ends and the block starts.
Any ideas or suggestions? Thank you in advance
Append: Just for the curious why I need this. We have a big json
schema running and have a small DSL that builds the json from the
model definitation. Without going to much into detail we wanted to
implement exportable_scopes.
class FooBar
exportable_scope :some_scope, title: 'Some Scope', -> { rewhere archived: true }
end
On some initializer this is supposed to happens:
def exportable_scope scope, attributes, &block
scope scope block
if attributes.any?
attributes.each do |attribute|
exportable_schema.scopes[scope] = attribute
end
else
exportable_schema.scopes[scope] = {title: scope}
end
end
So this is working fine, I just need a hint for the method
parameters.
Yes, it is possible.
When mixing different kinds of parameters, they have to be included in the method definition in a specific order:
Positional parameters (required and optional) and a single splat parameter, in any order;
Keyword parameters (required and optional), in any order;
Double splat parameter;
Block parameter (prefixed with &);
The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:
Required positional parameters;
Optional positional parameters (with default values);
Single splat parameter;
Keyword parameters (required and optional, their order is irrelevant);
Double splat parameter;
Explicit block parameter (prefixed with &).
Example:
def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
puts "Room items: #{room_items}"
puts "Periods: #{periods}"
b.call # Run the proc received through the &b parameter
end
meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }
# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block
Notice that when calling the method, we have:
Provided the cushion mandatory positional argument;
Overwritten the default value of the meditation optional positional argument;
Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
Provided the time mandatory keyword argument;
Omitted the posture optional keyword argument;
Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
Passed the block { puts "Hello from inside the block" } through the &b parameter;
Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.

I18n: how can I guard against empty interpolation arguments?

Given the following i18n key:
greeting: "Hi %{name}! Feeling %{adjective}?"
If I call I18n.t!("greeting", adjective: "strigine"), I get an exception: I18n::MissingInterpolationArgument. This is good.
However, if I pass no arguments at all, I just get the raw string.
I18n.t!("greeting") # => "Hi %{name}! Feeling %{adjective}?"
I want to ensure that this doesn't happen. Is there a version of this method call that raises if no arguments are given, or do I have to code it myself?
I18n does this on purpose
The reasons are documented in the tests as follows:
If no interpolation parameter is not given, I18n should not alter the
string. This behavior is due to three reasons:
Checking interpolation keys in all strings hits performance, badly;
This allows us to retrieve untouched values through I18n. For example I could have a middleware that returns I18n lookup results in
JSON to be processed through Javascript. Leaving the keys untouched
allows the interpolation to happen at the javascript level;
Security concerns: if I allow users to translate a web site, they can insert %{} in messages causing the I18n lookup to fail in every
request.
How to work around it
If you want an exception in this case, you can define a method like this:
# Needed because I18n will happily return the raw template string if given
# no interpolation arguments
# https://github.com/svenfuchs/i18n/blob/v0.7.0/lib/i18n/tests/interpolation.rb#L6-L21
def i18n_strict_t!(key, options = {})
localized = I18n.t!(key, options)
if missing = localized.match(I18n::INTERPOLATION_PATTERN)
fail I18n::MissingInterpolationArgument.new(
missing.captures.first, options, localized
)
end
localized
end
Usage:
i18n_strict_t!("greeting")
# => I18n::MissingInterpolationArgument: missing
# interpolation argument "name" in
# "Hi %{name}! Feeling %{adjective}?" ({} given)
i18n_strict_t!("greeting", name: "Carla", adjective: "taciturn")
# => "Hi Carla! Feeling taciturn?"
Or for slower but simpler implementation:
def i18n_strict_t!(key, options = {})
options[:force_interpolation] = true if options.empty?
I18n.t!(key, options)
end

Dynamically define many methods with same implementation but arbitrary arguments

I have many methods like these two:
def create_machine(name, os_type_id, settings_file='', groups=[], flags={})
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(soap_message))
end
def register_machine(machine)
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{__method__}".to_sym
args = method(__method__).parameters.map { |arg| arg[1] }
soap_message = Hash[args.map { |arg| [arg, eval(arg.to_s)] }]
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(soap_message))
end
They have the same implementation but different number of different arguments. There will be tens of such methods in each of tens of classes. So I thought I'd use some meta-programming to minimize the code repetition.
I was trying to do this via define_method and wanted to end up in something like this:
vb_method :create_machine, :args => [:name, :os_type_id], :optional_args => [:settings_file, :groups, :flags]
But I can't find a way to pass arbitrary number of named (non-splat) arguments to define_method (I thought splat argument will make documenting the methods hard to impossible also will make the resulting API inconvenient).
What would be the best way to deal with this (using Ruby 2.0)?
UPD
Another way to do this is defining a method vb_method:
def vb_method(*vb_meths)
vb_meths.each do |meth|
define_method(meth) do |message={}|
soap_method = "#{self.class.name.split('::').last.to_underscore}_#{meth}".to_sym
VirtualBoxAPI.send_request(#cl.conn, soap_method, #this.merge(message))
end
end
end
And then the class would have a call like this:
vb_method :create_machine, :register_machine
But is this case I will need to always call the methods with hash as an argument:
machine = vb.create_machine(name: 'my_vm', os_type_id: 'Windows95')
And that's exactly what I'm trying to avoid because I think in this case the resulting API can't be documented and is not convenient to use.
Stop trying to avoid option hashes. That's the "Ruby way" of doing things. They aren't impossible to document and several mainstream Ruby libraries use them this way (the first that come to mind are ActiveRecord and Mysql2).
Note that you can provide a default argument to the option hash, which serves as documentation and allows you to reduce code repetition.
Also, think about how your code would work if you could (somehow) pass an arbitrary number of named arguments to define_method. How would users remember which arguments are which? They would need to memorize the order and meaning of all the different positional arguments to all the different methods defined this way. When you have many similar methods with arguments of varying meanings, it's very difficult to keep everything straight. Keyword arguments (which is essentially what Ruby's option hashes are) were specifically created to avoid this situation.
If you're worried about error checking, define a helper method that checks the option hash for missing/unrecognized keys and raises an informative exception:
def validate_options(known, opts)
opts.each_key { |opt| raise "Unknown option: #{opt}" unless known.include?(opt) }
known.each { |opt, required| raise "Missing required option: #{opt}" if required and not opts.include?(opt) }
end

ruby send method passing multiple parameters

Trying to create objects and call methods dynamically by
Object.const_get(class_name).new.send(method_name,parameters_array)
which is working fine when
Object.const_get(RandomClass).new.send(i_take_arguments,[10.0])
but throwing wrong number of arguments 1 for 2 for
Object.const_get(RandomClass).new.send(i_take_multiple_arguments,[25.0,26.0])
The Random Class defined is
class RandomClass
def i_am_method_one
puts "I am method 1"
end
def i_take_arguments(a)
puts "the argument passed is #{a}"
end
def i_take_multiple_arguments(b,c)
puts "the arguments passed are #{b} and #{c}"
end
end
Can someone help me on how to send mutiple parameters to a ruby method dynamically
send("i_take_multiple_arguments", *[25.0,26.0]) #Where star is the "splat" operator
or
send(:i_take_multiple_arguments, 25.0, 26.0)
You can alternately call send with it's synonym __send__:
r = RandomClass.new
r.__send__(:i_take_multiple_arguments, 'a_param', 'b_param')
By the way* you can pass hashes as params comma separated like so:
imaginary_object.__send__(:find, :city => "city100")
or new hash syntax:
imaginary_object.__send__(:find, city: "city100", loc: [-76, 39])
According to Black, __send__ is safer to namespace.
“Sending is a broad concept: email is sent, data gets sent to I/O sockets, and so forth. It’s not uncommon for programs to define a method called send that conflicts with Ruby’s built-in send method. Therefore, Ruby gives you an alternative way to call send: __send__. By convention, no one ever writes a method with that name, so the built-in Ruby version is always available and never comes into conflict with newly written methods. It looks strange, but it’s safer than the plain send version from the point of view of method-name clashes”
Black also suggests wrapping calls to __send__ in if respond_to?(method_name).
if r.respond_to?(method_name)
puts r.__send__(method_name)
else
puts "#{r.to_s} doesn't respond to #{method_name}"
end
Ref: Black, David A. The well-grounded Rubyist. Manning, 2009. P.171.
*I came here looking for hash syntax for __send__, so may be useful for other googlers. ;)

How to pass an optional argument into a Ruby method?

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

Resources