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
Related
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.
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
I have this code:
Firm.all.each do |firm|
url = firm.site
doc = Nokogiri::HTML(open(url))
data = doc.css("##{firm.menu_id} a")
data.each do |e|
e.text.strip!
e.text.gsub!(/[\n\t]*/,'')
puts e.text
end
end
The strings are being displayed in the same format as the input (that means, the gsub! method is not affecting the string). I think that e.text can be immutable, but I'd like to ensure that.
The text method returns a new String each time, which can be seen using object_id:
e = Nokogiri::XML('<a>text</a>')
e.text.object_id == e.text.object_id # => false
If you want to modify the node's text, set the content:
e.at_css('a').content = "foo"
e.text # => "foo"
I have a terribly nested Json response.
[[{:test=>[{:id=>1, :b=>{id: '2'}}]}]]
There's more arrays than that but you get the idea.
Is there a way to recursively search through and find all the items that have a key I need?
I tried using this function extract_list() but it doesn't handle arrays well.
def nested_find(obj, needed_keys)
return {} unless obj.is_a?(Array) || obj.is_a?(Hash)
obj.inject({}) do |hash, val|
if val.is_a?(Hash) && (tmp = needed_keys & val.keys).length > 0
tmp.each { |key| hash[key] = val[key] }
elsif val.is_a?(Array)
hash.merge!(obj.map { |v| nested_find(v, needed_keys) }.reduce(:merge))
end
hash
end
end
Example
needed_keys = [:id, :another_key]
nested_find([ ['test', [{id:1}], [[another_key: 5]]]], needed_keys)
# {:id=>1, :another_key=>5}
The following is not what I'd suggest, but just to give a brief alternative to the other solutions provided:
2.1.1 :001 > obj = [[{:test=>[{:id=>1, :b=>{id: '2'}}]}]]
=> [[{:test=>[{:id=>1, :b=>{:id=>"2"}}]}]]
2.1.1 :002 > key = :id
=> :id
2.1.1 :003 > obj.inspect.scan(/#{key.inspect}=>([^,}]*)[,}]/).flatten.map {|s| eval s}
=> [1, "2"]
Note: use of eval here is just for an example. It would fail/produce incorrect results on anything whose inspect value was not eval-able back to the same instance, and it can execute malicious code:
You'll need to write your own recursive handler. Assuming that you've already converted your JSON to a Ruby data structure (via JSON.load or whatnot):
def deep_find_value_with_key(data, desired_key)
case data
when Array
data.each do |value|
if found = deep_find_value_with_key value, desired_key
return found
end
end
when Hash
if data.key?(desired_key)
data[desired_key]
else
data.each do |key, val|
if found = deep_find_value_with_key(val, desired_key)
return found
end
end
end
end
return nil
end
The general idea is that given a data structure, you check it for the key (if it's a hash) and return the matching value if found. Otherwise, you iterate it (if it's an Array or Hash) and perform the same check on each of it's children.
This will find the value for the first occurrence of the given key, or nil if the key doesn't exist in the tree. If you need to find all instances then it's slightly different - you basically need to pass an array that will accumulate the values:
def deep_find_value_with_key(data, desired_key, hits = [])
case data
when Array
data.each do |value|
deep_find_value_with_key value, desired_key, hits
end
when Hash
if data.key?(desired_key)
hits << data[desired_key]
else
data.each do |key, val|
deep_find_value_with_key(val, desired_key)
end
end
end
return hits
end
i have a json with duplicate keys as below:
{"a":{
"stateId":"b",
"countyId":["c"]
},"a":{
"stateId":"d",
"countyId":["e"]
}}
When i use JSON.parse or JSON(stirng), it parses and gives me the key with values d, e. I need to parse the json such that it it avoids parsing the same key twice and has b, c values for the key 'a' instead of 'd', 'e'.
There is a way. Instead of using the ususal Hash class for parsing JSON objects, use a slightly modified class, which can check if a key already exists:
class DuplicateCheckingHash < Hash
attr_accessor :duplicate_check_off
def []=(key, value)
if !duplicate_check_off && has_key?(key) then
fail "Failed: Found duplicate key \"#{key}\" while parsing json! Please cleanup your JSON input!"
end
super
end
end
json = '{"a": 1, "a": 2}' # duplicate!
hash = JSON.parse(json, { object_class:DuplicateCheckingHash }) # will raise
json = '{"a": 1, "b": 2}'
hash = JSON.parse(json, { object_class:DuplicateCheckingHash })
hash.duplicate_check_off = true # make updateable again
hash["a"] = 42 # won't raise
It all depends on the format of your string. If it's as simple as you posted:
require 'json'
my_json =<<END_OF_JSON
{"a":{
"stateId":"b",
"countyId":["c"]
},"a":{
"stateId":"d",
"countyId":["e"]
},"b":{
"stateId":"x",
"countyId":["y"]
},"b":{
"stateId":"no",
"countyId":["no"]
}}
END_OF_JSON
results = {}
hash_strings = my_json.split("},")
hash_strings.each do |hash_str|
hash_str.strip!
hash_str = "{" + hash_str if not hash_str.start_with? "{"
hash_str += "}}" if not hash_str.end_with? "}}"
hash = JSON.parse(hash_str)
hash.each do |key, val|
results[key] = val if not results.keys.include? key
end
end
p results
--output:--
{"a"=>{"stateId"=>"b", "countyId"=>["c"]}, "b"=>{"stateId"=>"x", "countyId"=>["y"]}}