Ruby Savon Requests - ruby

#rubiii has previously shown (Savon soap body problem) that you can customize Savon requests with
class SomeXML
def self.to_s
"<some>xml</some>"
end
end
client.request :some_action do
soap.body = SomeXML
end
But why would you use a class method like this? It would seem more likely that you would ask an instance of a class to turn itself into a hash for the request body. i.e.
#instance = SomeClass.new
client.request :some_action do
soap.body = #instance.to_soap
end
However, when I try doing this, #instance variable isn't in 'scope' within the request block. So I get a can't call method to_soap on nil. But if instead I use a class method then I can get it to work. i.e.
class SomeClass
##soap_hash = nil
def self.soap_hash=(hash)
##soap_hash = hash
end
def self.soap_hash
##soap_hash
end
end
SomeClass.soap_hash = #instance.to_soap
client.request :some_action do
soap.body = SomeClass.soap_hash
end
I don't get it?

The class-method example was just that, an example. Feel free to use any object that responds to to_s.
The block is processed via instance_eval with delegation, which is why you can only use local variables and methods inside the block. If you need to use instance variables, change your block to accept arguments. Savon will notice that you specified arguments and yield those values instead of evaluating the block.
For information about which arguments to specifiy and everything else, please RTFM ;)

Related

How to test ruby module methods with block using Rspec?

I want to test a following method, which calls a module method with a block.
def test_target
MyModule.send do |payload|
payload.my_text = "payload text"
end
end
MyModule's structure is like following.
module MyModule
class Payload
attr_accessor :my_text
def send
# do things with my_text
end
end
class << self
def send
payload = Payload.new
yield payload
payload.send
end
end
How can I test whether MyModule receives send method with a block, which assigns "payload text" to payload.my_text?
Currently I'm only testing expect(MyModule).to receive(:send).once. I looked through and tried Rspec yield matchers but cannot get things done. (Maybe I've ben searching for wrong keywords..)
The easiest way is to insert a double as the yield argument, which you can make an assertion on.
payload = Payload.new
allow(Payload).to receive(:new).and_return(payload)
test_target
expect(payload.my_text).to eq 'payload text'
Alternatively you could also use expect_any_instance_of, but I'd always prefer to use a specific double instead.
I would mock MyModule to yield another mock, that would allow speccing that my_text= is called on the yielded object.
let(:payload) { instance_double('Payload') }
before do
allow(MyModule).to receive(:send).and_yield(payload)
allow(payload).to receive(:my_text=).and_return(nil)
end
# expectations
expect(MyModule).to have_received(:send).once
expect(payload).to have_received(:my_text=).with('payload text').once

Delegation in Ruby

I have a class Klass, and its constructor accepts an argument. We should be able to call methods on this object that are not defined in Klass.
We can chain multiple methods, but in the end, we have to use Klass#result to get the result like:
Klass.new(5).pred.pred.result
and the output here should be 3. I tried using method_missing in Klass and using send on the object's class, but that would have worked without the result method that I have to use. Can someone explain how this can be done with delegation?
You could do something like this:
class Klass
def initialize(number)
#number = number
end
def result
#number
end
def method_missing(method_name, *arguments, &block)
if #number.respond_to?(method_name)
#number = #number.method(method_name).call(*arguments, &block)
return self
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
# be sure to implement this...
end
end
puts Klass.new(5).pred.pred.result # => 3
But it's problematic. In this particular example, since #pred returns a new object (it doesn't modify the object it was called on), we have to reassign the instance variable to the result. It works for pred and other methods that return new Integers, but some methods on Integer don't return an Integer (e.g. Integer#even). In this case you'd get this sort of behavior:
puts Klass.new(4).even?.result # => true
Depending on your particular situation, that might be what you're after. Or, it might be that in your situation all methods the object being delegated to mutate that object, rather than return new instances of the object, in which case the reassignment isn't needed.
I don't think you can use Ruby's existing Delegator and SimpleDelegator constructs, because the only way you can chain the final #result call onto the end is if every delegated call returns the instance of Klass. Using those existing constructs would cause delegated calls to return their normal return values, and the chaining would then be on whatever objects those return values return. For example, using the above code, you'd see this behavior:
puts Klass.new(5).pred.pred.class # => "Klass"
Using SimpleDelegator, you'd see this behavior
require 'delegate'
class Klass2 < SimpleDelegator
# Klass2 methods...
end
puts Klass2.new(5).pred.pred.class # => "Fixnum"
Hope that helps.

what does rack provide to each in its middleware

I saw the following code posted on railscasts:
def each(&block)
block.call("<!-- #{#message}: #{#stop - #start} -->\n") if #headers["Content-Type"].include? "text/html"
#response.each(&block)
end
I was wondering where the &block was coming from? What's happening here?
&block is the way to define a method in Ruby that accepts a method and to explicitly assign the block to the variable referenced by the &.
Specifically, what that code does, it to accept a block, call the block passing a string as argument, then passing the block to the inner response.
At first glance, I would say that the specific middleware you saw is basically injecting the string
"<!-- #{#message}: #{#stop - #start} -->\n"
into the HTTP response body if the request content type is "text/html", which means an HTTP page.
If the request is for an HTTP page, than the response is served and that string is injected. Otherwise, if the request is for another kind of file (for instance a binary file, a text file, a javascript, etc), then the response is served without any modification.
The following example will help you to understand a little bit more the scope of &block
def foo(&block)
puts "foo"
bar(&block)
end
def bar(&block)
puts "bar"
block.call
end
foo do
puts "block"
end
# => foo
# => bar
# => block
Also note that the variable holding the block can be named anything, not necessary block
def foo(&my_block)
puts "foo"
bar(&my_block)
end
def bar(&another_block)
puts "bar"
another_block.call
end
foo do
puts "block"
end
# => foo
# => bar
# => block
On the given example each is defined to make the middleware behaves as a valid rack response body too (I really don't recommend that).
Rack expect the response body to be an instance of a class that defines each as a public method (an Array or even an ActionController::Response instance).
Any middleware after that one may use that each to operate over the response body. At the end of the middlewares chain rack itself will use it to process and dispatch the final response.

Ruby: mock a local object to test module methods

Working in Sinatra, a local object request is created and made available to all views and helpers. So, I can make an ApplicationHelper module with helper methods, and if the helper methods are called in the view they can in turn call the request object, like so:
module ApplicationHelper
def nav_link_to(text,path)
path == request.path_info ? klass = 'class="current"' : klass = ''
%Q|<a href="#{path}" #{klass}>#{text}</a>|
end
end
Now, I want to test this, but in my test the request object doesn't exist. I tried to mock it, but that didn't work. Here's my test so far:
require 'minitest_helper'
require 'helpers/application_helper'
describe ApplicationHelper do
before :all do
#helper = Object.new
#helper.extend(ApplicationHelper)
end
describe "nav links" do
before :each do
request = MiniTest::Mock.new
request.expect :path_info, '/'
end
it "should return a link to a path" do
#helper.nav_link_to('test','/test').must_equal 'test'
end
it "should return an anchor link to the current path with class 'current'" do
#helper.nav_link_to('test','/').must_equal 'test'
end
end
end
So, how can you mock a 'local' object so that the code your testing can call it?
You need to make sure there is a request method on your #helper object that returns the mock request object.
In RSpec I'd just stub it. I'm not particularly familiar with Minitest, but a quick look suggests that this might work in recent versions (if you change request to #request in your before :each):
it "should return a link to a path" do
#helper.stub :request, #request do
#helper.nav_link_to('test','/test').must_equal 'test'
end
end
Update
Since Minitest requires that the stubbed method is already defined on the object, you could make #helper an instance of Struct.new(:request) instead of Object, i.e.
#helper = Struct.new(:request).new
And actually, having done that, you might not need the stub at all! You could just do
before :each do
#helper.request = MiniTest::Mock.new
#helper.request.expect :path_info, '/'
end

HTTParty - JSON to strongly typed object

Is it possible to have HTTParty deserialize the results from a GET to a strongly typed ruby object? For example
class Myclass
include HTTParty
end
x = Myclass.get('http://api.stackoverflow.com/1.0/questions?tags=HTTParty')
puts x.total
puts x.questions[0].title
Right now it deserializes it into a hash
puts x["total"]
My question is actually if HTTParty supports this OTB, not by installing additional gems.
Edit:
I'm still new to Ruby, but I recall that class fields are all private so they would need to be accessed through getter/setter methods. So maybe this question isn't a valid one?
If you are just wanting method syntax, you can use an open struct.
require 'httparty'
require 'ostruct'
result = HTTParty.get 'http://api.stackoverflow.com/1.0/questions?tags=HTTParty'
object = OpenStruct.new result
object.total # => 2634237
A possible downside is that this object is totally open such that if you invoke a nonexistent method on it, it will just return nil (if you invoke a setter, it will create both the setter and getter)
It sounds like you want the return value of Myclass::get to be an instance of Myclass. If that's the case, you could cache the return value from the HTTP request and implement method_missing to return values from that hash:
class Myclass
include HTTParty
attr_accessor :retrieved_values
def method_missing(method, *args, &block)
if retrieved_values.key?(method)
retrieved_values[method]
else
super
end
end
def self.get_with_massaging(url)
new.tap do |instance|
instance.retrieved_values = get_without_massaging(url)
end
end
class << self
alias_method :get_without_massaging, :get
alias_method :get, :get_with_massaging
end
end
This isn't exactly what you asked for, because it only works one level deep — i.e., x.questions[0].title would need to be x.questions[0][:title]
x = Myclass.get('http://api.stackoverflow.com/1.0/questions?tags=HTTParty')
p x.total
p x.questions[0][:title]
Perhaps you could come up with some hybrid of this answer and Joshua Creek's to take advantage of OpenStruct.
I should also point out that all the method aliasing trickery isn't necessary if your method doesn't have to be named get.

Resources