I'm trying to pass some data as a block to some external API. It would be a hassle to accommodate it to accepting additional parameters. If it were javascript, I might make it like so:
var callback = function() {
// do something
}
callback['__someData'] = options;
someExternalAPI(callback);
Is this possible with Ruby? Or how should I go about associating some data with a block?
Not sure if the edits to the question were correct. First, I'd like to specifically pass some data along with a block if that is possible. Not sure if it is though. And probably the only way to do it in ruby is to pass some data as a block.
Additionally, here might be some useful info.
Okay, it probably makes sense to show the whole picture. I'm trying to adapt webmock to my needs. I have a function, which checks if request's params (be them of POST, or of GET) match specified criteria:
def check_params params, options
options.all? do |k,v|
return true unless k.is_a? String
case v
when Hash
return false unless params[k]
int_methods = ['<', '<=', '>', '>=']
v1 = int_methods.include?(v.first[0]) ? params[k].to_i : params[k]
v2 = int_methods.include?(v.first[0]) \
? v.first[1].to_i : v.first[1].to_s
v1.send(v.first[0], v2)
when TrueClass, FalseClass
v ^ ! params.key?(k)
else
params[k] == v.to_s
end
end
end
It's not perfect, but it suffices for my particular needs, for now. I'm calling it like this:
stub_request(:post, 'http://example.com/')
.with { |request|
check_params Rack::Utils.parse_query(request.body), options
}
And the thing is generally I see no sensible way to output with block conditions. But in my particular case one can just output options hash. And instead of this:
registered request stubs:
stub_request(:post, "http://example.com")
to have this:
stub_request(:post, "http://example.com").
with(block: {"year"=>2015})
Which is what I'm trying to do.
Okay, I ended up doing this:
p = Proc.new {}
p.class.module_eval { attr_accessor :__options }
p.__options = {a: 1}
# ...
pp p.__options
Or to be more specific:
def mk_block_cond options, &block_cond
options = options.select { |k,v| ! k.is_a?(Symbol) }
return block_cond if options.empty?
block_cond.class.module_eval { attr_accessor :__options }
block_cond.__options = options
block_cond
end
module WebMock
class RequestPattern
attr_reader :with_block
end
end
module StubRequestSnippetExtensions
def to_s(with_response = true)
request_pattern = #request_stub.request_pattern
string = "stub_request(:#{request_pattern.method_pattern.to_s},"
string << " \"#{request_pattern.uri_pattern.to_s}\")"
with = ""
if (request_pattern.body_pattern)
with << ":body => #{request_pattern.body_pattern.to_s}"
end
if (request_pattern.headers_pattern)
with << ",\n " unless with.empty?
with << ":headers => #{request_pattern.headers_pattern.to_s}"
end
if request_pattern.with_block \
&& request_pattern.with_block.respond_to?('__options') \
&& request_pattern.with_block.__options
with << ",\n " unless with.empty?
with << "block: #{request_pattern.with_block.__options}"
end
string << ".\n with(#{with})" unless with.empty?
if with_response
string << ".\n to_return(:status => 200, :body => \"\", :headers => {})"
end
string
end
end
module WebMock
class StubRequestSnippet
prepend StubRequestSnippetExtensions
end
end
module RequestPatternExtensions
def to_s
string = "#{#method_pattern.to_s.upcase}"
string << " #{#uri_pattern.to_s}"
string << " with body #{#body_pattern.to_s}" if #body_pattern
string << " with headers #{#headers_pattern.to_s}" if #headers_pattern
if #with_block
if #with_block.respond_to?('__options') \
&& #with_block.__options
string << " with block: %s" % #with_block.__options.inspect
else
string << " with given block"
end
end
string
end
end
module WebMock
class RequestPattern
prepend RequestPatternExtensions
end
end
And now I stub requests this way:
stub_request(:post, 'http://example.com/')
.with &mk_block_cond(options) { |request|
check_params Rack::Utils.parse_query(request.body), options
}
P.S. github issue
Related
I have a Hash which needs to be converted in a String with escaped characters.
{name: "fakename"}
and should end up like this:
'name:\'fakename\'
I don't know how this type of string is called. Maybe there is an already existing method, which I simply don't know...
At the end I would do something like this:
name = {name: "fakename"}
metadata = {}
metadata['foo'] = 'bar'
"#{name} AND #{metadata}"
which ends up in that:
'name:\'fakename\' AND metadata[\'foo\']:\'bar\''
Context: This query a requirement to search Stripe API: https://stripe.com/docs/api/customers/search
If possible I would use Stripe's gem.
In case you can't use it, this piece of code extracted from the gem should help you encode the query parameters.
require 'cgi'
# Copied from here: https://github.com/stripe/stripe-ruby/blob/a06b1477e7c28f299222de454fa387e53bfd2c66/lib/stripe/util.rb
class Util
def self.flatten_params(params, parent_key = nil)
result = []
# do not sort the final output because arrays (and arrays of hashes
# especially) can be order sensitive, but do sort incoming parameters
params.each do |key, value|
calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
if value.is_a?(Hash)
result += flatten_params(value, calculated_key)
elsif value.is_a?(Array)
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
end
end
result
end
def self.flatten_params_array(value, calculated_key)
result = []
value.each_with_index do |elem, i|
if elem.is_a?(Hash)
result += flatten_params(elem, "#{calculated_key}[#{i}]")
elsif elem.is_a?(Array)
result += flatten_params_array(elem, calculated_key)
else
result << ["#{calculated_key}[#{i}]", elem]
end
end
result
end
def self.url_encode(key)
CGI.escape(key.to_s).
# Don't use strict form encoding by changing the square bracket control
# characters back to their literals. This is fine by the server, and
# makes these parameter strings easier to read.
gsub("%5B", "[").gsub("%5D", "]")
end
end
params = { name: 'fakename', metadata: { foo: 'bar' } }
Util.flatten_params(params).map { |k, v| "#{Util.url_encode(k)}=#{Util.url_encode(v)}" }.join("&")
I use it now with that string, which works... Quite straigt forward:
"email:\'#{email}\'"
email = "test#test.com"
key = "foo"
value = "bar"
["email:\'#{email}\'", "metadata[\'#{key}\']:\'#{value}\'"].join(" AND ")
=> "email:'test#test.com' AND metadata['foo']:'bar'"
which is accepted by Stripe API
I am trying to implement a trie in Ruby but can't figure out what the problem is with my print + collect methods.
I just implemented the same in JS and working fine. I guess the issue could be that Ruby is passed by reference (unlike JS) and how variable assignment works in Ruby.
So if I run the code with string.clone as argument when I recursively call the collect function then I get:
["peter", "peter", "petera", "pdanny", "pdjane", "pdjanck"]
and if I pass string then:
["peterradannyjaneck", "peterradannyjaneck", "peterradannyjaneck", "peterradannyjaneck", "peterradannyjaneck", "peterradannyjaneck"]
Any ideas how to fix this?
the code:
class Node
attr_accessor :hash, :end_node, :data
def initialize
#hash = {}
#end_node = false
#data = data
end
def end_node?
end_node
end
end
class Trie
def initialize
#root = Node.new
#words = []
end
def add(input, data, node = #root)
if input.empty?
node.data = data
node.end_node = true
elsif node.hash.keys.include?(input[0])
add(input[1..-1], data, node.hash[input[0]])
else
node.hash[input[0]] = Node.new
add(input[1..-1], data, node.hash[input[0]])
end
end
def print(node = #root)
collect(node, '')
#words
end
private
def collect(node, string)
if node.hash.size > 0
for letter in node.hash.keys
string = string.concat(letter)
collect(node.hash[letter], string.clone)
end
#words << string if node.end_node?
else
string.length > 0 ? #words << string : nil
end
end
end
trie = Trie.new
trie.add('peter', date: '1988-02-26')
trie.add('petra', date: '1977-02-12')
trie.add('danny', date: '1998-04-21')
trie.add('jane', date: '1985-05-08')
trie.add('jack', date: '1994-11-04')
trie.add('pete', date: '1977-12-18')
print trie.print
Ruby's string concat mutates the string and doesn't return a new string. You may want the + operator instead. So basically change the 2 lines inside collect's for-loop as per below:
stringn = string + letter
collect(node.hash[letter], stringn)
Also, you probably want to either always initialize #words to empty in print before calling collect, or make it a local variable in print and pass it to collect.
In the code below, the initial get '/' contains a form, whose action is post '/'. when the user inputs a number, it should be converted to a variable that will be used to call the Game class, for which I have generated another action to reveal a new form at get '/game'. the variable generated in the post method is not being stored. how can I both store the variable created in post and then link into the get '/game' action?
require 'sinatra'
require 'sinatra/reloader'
##count = 5
Dict = File.open("enable.txt")
class Game
attr_accessor :letters, :number, :guess, :disp
##count = 5
def initialize (number)
letters = find(number)
end
def find (n)
words =[]
dictionary = File.read(Dict)
dictionary.scan(/\w+/).each {|word| words << word if word.length == n}
letters = words.sample.split("").to_a
letters
end
def counter
if letters.include?guess
correct = check_guess(guess, letters)
else
##count -= 1
end
end
end
get '/' do
erb :index
end
post '/' do
n = params['number'].to_i
#letters = Game.new(n)
redirect '/game'
end
get "/game" do
guess = params['guess']
letters = #letters
if guess != nil
correct = check_guess(guess, letters)
end
disp = display(letters, correct)
erb :game, :locals => {:letters => letters, :disp => disp}
end
def display(letters, correct)
line = "__"
d=[]
letters.each do |x|
if correct == nil
d << line
elsif correct.include?x
d << x
else
d << line
end
end
d.join(" ")
end
def check_guess(guess, letters)
correct = []
if guess != nil
if letters.include?guess
correct << guess
end
end
correct
end
You cannot do this:
#letters = Game.new(n)
each time you create a request, and new Request instance created and so the #letters attribute no longer exists.
It's the equivalent of
r = Request.new()
r.letters = Game.new()
r = Request.new()
r.letters # not defined anymore!!
You could achieve what you want using a class variable instead
##letters = Game.new(n)
Although this will become a nightmare when you have multiple users and will only work when you have a single ruby server process.
A more advanced approach would be to store params['number'] in a session cookie or in a database.
UPDATE: OK, so I implemented your code, but now the indentation is not showing up! Any ideas what might be wrong? I modified the code so that it would attempt to pass my original test (this is only an exercise so in real life I would not be overriding the XmlDocument class) and here is the modified code:
class XmlDocument
attr_reader :indent_depth, :bool
def initialize(bool = false, indent_depth = 0)
#indent_depth = indent_depth
#bool = bool
end
def method_missing(name, *args)
indentation = ' '*indent_depth
attrs = (args[0] || {}).map { |k, v| " #{k}='#{v}'" }.join(' ')
if block_given?
puts indent_depth
opening = "#{indentation}<#{name}#{attrs}>"
contents = yield(XmlDocument.new(true,indent_depth+1))
closing = "#{indentation}</#{name}>"
bool ? opening + "\n" + contents + "\n" + closing : opening + contents + closing
else
"#{indentation}<#{name}#{attrs}/>"
end
end
end
I'm trying to get the method to pass this test:
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
...but I'm unsure as to where to go with my code, below. I was thinking of using a counter to keep track of how far indented we have to go. I tried some code, but then deleted it because it was getting too messy and I have a feeling that the indentation should not be too complicated to implement.
class XmlDocument
def initialize(bool = false)
#bool = bool
end
def send(tag_name)
"<#{tag_name}/>"
end
def method_missing(meth, arg={}, &block)
arbitrary_method = meth.to_s
tag_string = ''
# 1) test for block
# 2) test for arguments
# 3) test for hash
if block_given? # check for #xml.hello do; #xml.goodbye; end
if yield.class == String # base case: #xml.hello do; "yellow"; end
"<#{arbitrary_method}>#{yield}</#{arbitrary_method}>"
else # in the block we do not have a string, we may have another method
method_missing(yield)
end
elsif arg.empty? # no arguments e.g. #xml.hello
send(arbitrary_method)
else # hash as argument e.g. #xml.hello(:name => 'dolly')
send("#{arbitrary_method} #{arg.keys[0]}='#{arg.values[0]}'")
end
end
end
Your code needs a lot of work - some pointers:
Do not override the send method!
Don't call yield over and over - you don't know what side effects you might cause, not to mention a performance hit - call it once, and remember the return value.
You might want to read up on how to write a DSL (here is a blogpost on the subject), to see how it was done correctly in other places.
Ignoring the above, I will try to answer your question regarding indentation.
In a DSL use case, you might want to use a context object which holds the indentation depth as state:
class Indented
attr_reader :indent_depth
def initialize(indent_depth = 0)
#indent_depth = indent_depth
end
def method_missing(name, *args)
indentation = ' ' * indent_depth
attrs = (args[0] || {}).map { |k, v| "#{k}='#{v}'" }.join(' ')
if block_given?
"#{indentation}<#{name} #{attrs}>\n" +
yield(Indented.new(indent_depth + 1)) +
"\n#{indentation}</#{name}>"
else
"#{indentation}<#{name} #{attrs}/>"
end
end
end
xml = Indented.new
puts xml.hello do |x|
x.goodbye do |x|
x.come_back do |x|
x.ok_fine(:be => "that_way")
end
end
end
# => <hello >
# => <goodbye >
# => <come_back >
# => <ok_fine be='that_way'/>
# => </come_back>
# => </goodbye>
# => </hello>
i have this test in ruby I'm trying to implement
require "silly_blocks"
describe "some silly block functions" do
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
it "reverses each word in the string returned by the default block" do
result = reverser do
"hello dolly"
end
result.should == "olleh yllod"
end
end
here's the method
def do_reverse(str)
str = str.split
first_str = str[0].reverse
second_str= str[1]
if (second_str == nil)
str = first_str.to_s
else
second_str = str[1].reverse
str = (first_str +" "+ second_str)
end
end
what is the best way that i could implement it . when i try to rake the test it failed , but the method by itself return the reserve. i'm just a little confused.
Try this code:
def reverser
yield.split.map { |word| word.reverse}.join(" ")
end
Here's an easy way of doing what you're looking for, with specs.
# lib/reverse_words.rb
def reverse_words(phrase)
return '' if phrase.nil?
words = phrase.split
phrase.split.map(&:reverse!).join(' ')
end
def reverser
reverse_words(yield)
end
# spec/reverse_words_spec.rb
describe "#reverse_words" do
context "when single word" do
subject { reverse_words("hello") }
it { should == "olleh" }
end
context "when multiple words" do
subject { reverse_words("hello dolly") }
it { should == "olleh yllod" }
end
context "when nil" do
subject { reverse_words(nil) }
it { should == '' }
end
context "when empty" do
subject { reverse_words('') }
it { should == '' }
end
end
Note that the reverser spec simply makes use of the behavior that reverse_words has already been specced to pass.
describe "#reverser" do
subject do
reverser do
"this is a test"
end
end
it { should == reverse_words("this is a test") }
end
Here's a less wordy reverse_words spec:
describe "#reverse_words (less wordy)" do
# counterintuitive keys as answers to support the nil case
cases = { "olleh" => "hello",
"olleh yllod" => "hello dolly",
'' => nil,
'' => ''
}
cases.each_pair do |expected, input|
context "#{input} should equal #{expected}" do
subject { reverse_words(input) }
it { should == expected }
end
end
end
This works. The data you want is stored in "yield".
def reverser
yield.gsub(/\w+/) { |w| w.each_char.to_a.reverse.join }
end
My reverser method:
def reverser
# yield is the string given in the block
words = yield.split(' ')
final = []
words.each do |word|
final.push(word.reverse)
end
final.join(' ')
end
So. I came here looking for information on how to do this also. As the language wasn't clear. I went and looked offsite, and found enough information to pass the tests.
So, blocks are those things between curly braces that sometimes follow functions in ruby, such as
list.each {|i| i.reverse}
So what the spec is doing is trying to figure out what happens when it does:
rerverser {"hello"}
Putting yield in a function just returns whatever is in the block, so
def print_block
puts yield
end
print_block {"Hello world."}
#=> "Hello world"
Then you can just manipulate yield like you would manipulate any argument. There's a lot more to blocks. Here's a good place to start, but that's all you need to know to solve the exercise if you've solved all of Test First's learn_ruby exercises up until now.