Stringifying query parameters: can we go further? - ruby

I wrote this method:
def stringify_query_params(query_parameters)
stringified_query_params = ''
query_parameters.each_with_index do |kv, i|
k, v = kv
index = i
if index == 0
stringified_query_params += "?#{k}=#{v}"
else
stringified_query_params += "&#{k}=#{v}"
end
end
return stringified_query_params
end
RubyCop is complaining in my running instance of RubyMine saying that I should instead be capturing the output of the conditional branching logic like this.
I was able to make it slightly better, using some methods in the Enumerable module
def stringify_query_parameters(query_parameters)
query_parameters.each_with_object([]).with_index do |((k, v), acc), index|
acc.push((index.positive? ? '&' : '?') + "#{k}=#{v}")
end.join('')
end
Can anyone think of a way to make it even terser?

It can be as follows:
def stringify_query_parameters(query_parameters)
'?' + query_parameters.map{ |k, v| "#{k}=#{v}" }.join('&')
end

Related

how to apply a function to each values of `params`

I am trying to pre-process the params with to_ar2en_i function in ApplicationController before any action processes the params, I have the following in my application_controller.rb:
# translates every params' entity from arabic to english
before_action :param_convert_ar2en_i
private
def param_convert_ar2en_i h = nil, path = []
h ||= params
h.each_pair do |k, v|
if v.respond_to?(:key?)
param_convert_ar2en_i v, [path, k].flatten
else
# something like:
params[[path, k].flatten].to_ar2en_i
end
end
end
The problem is I don't know how to apply to_ar2en_i to a nested params with the path of [[path, k].flatten].
Can anybody kindly help me on this?
Silly me!! Instead of trying to access params in params[[path, k].flatten].to_ar2en_i all I needed to do is just to access h[k].to_ar2en_i and since the h is passed by reference in Ruby, it will do the job.
def param_convert_ar2en_i h = nil, l = [], depth: 0
h ||= params
h.each_pair do |k, v|
if v.respond_to? :key?
param_convert_ar2en_i v, [l, k].flatten, depth: depth + 1
else
if h[k].respond_to? :each
h[k].each { |i| i.to_ar2en_i if i.respond_to? :to_ar2en_i }
else
h[k].to_ar2en_i if h[k].respond_to? :to_ar2en_i
end
end
end
end

Need help indenting tags in the output in Ruby

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>

Reverse words of a string in Ruby?

I am trying to reverse the words of a string in Ruby, without using the reverse method. I want to implement the known algorithm of:
Reverse the whole string
Reverse each word in the reversed string.
Here is what I have come up with:
class String
def custom_reverse(start, limit)
i_start = start
i_end = limit - 1
while (i_start <= i_end)
tmp = self[i_start]
self[i_start] = self[i_end]
self[i_end] = tmp
i_start += 1
i_end -= 1
end
return self
end
def custom_reverse_words
self.custom_reverse(0, self.size)
i_start = 0
i_end = 0
while (i_end <= self.length)
if (i_end == self.length || self[i_end] == ' ')
self.custom_reverse(i_start, i_end)
i_start += 1
end
i_end += 1
end
end
end
test_str = "hello there how are you"
p test_str.custom_reverse_words
But the results are "yahthello ow ou er ereh"
What am I missing?
The gist of any reverse operation is to iterate over elements in the reverse order of what you'd normally do. That is, where you'd usually use the set (0..N-1) you'd instead go through (N-1..0) or more specifically N-1-i where i is 0..N-1:
class String
def reverse_words
split(/\s+/).map{|w|wl=w.length-1;(0..wl).map{|i|w[wl-i]}.join}.join(' ')
end
end
puts "this is reverse test".reverse_words.inspect
# => "siht si esrever tset"
The same principle can be applied to the words in a given string.
Interview questions of this sort are of highly dubious value. Being "clever" in production code is usually a Very Bad Idea.
Here's one way to reverse an array without using the built-in reverse:
class Array
def reverse
tmp_ary = self.dup
ret_ary = []
self.size.times do
ret_ary << tmp_ary.pop
end
ret_ary
end
end
%w[a b c].reverse # => ["c", "b", "a"]
tmp_ary.pop is the secret. pop removes elements from the end of the array.
The cleanest solution I could think of is:
class Array
def my_reverse
sort_by.with_index {|_, i| -i}
end
end
class String
def words
split(/\W+/)
end
def revert_words
words.my_reverse.join(' ')
end
def revert_each_word
words.map {|w| w.chars.my_reverse.join}.join(' ')
end
end
Once you define a simple and efficient array reverser:
def reverse_array(a)
(a.length / 2).times {|i| a[i],a[-(i+1)] = a[-(i+1)],a[i]}
a
end
You can reverse a sentence pretty straightforwardly:
def reverse_sentence(s)
reverse_array(s.split('')).join.split(" ").map{|w| reverse_array(w.split('')).join}.join(" ")
end
reverse_sentence "Howdy pardner" # => "pardner Howdy"
Here's another way:
class String
def reverse_words
split.inject([]){|str, word| str.unshift word}.join(' ')
end
def reverse_chars
each_char.inject([]){|str, char| str.unshift char}.join('')
end
end
Revised
Carey raises a good point, reverse_chars can be simplified, since string is already an Enumerable:
class String
def reverse_chars
each_char.inject(""){|str, char| str.insert(0, char) }
end
end

Functional approach to joining an Array of values with varying separator?

I'm looking for a functional way to perform this messy procedural logic:
values = [a, b, c, d, e, f]
last_value = nil
string = ""
values.each do |v|
string << if last_value && last_value.special?
"/x/" + v.name.to_s
else
"/" + v.name.to_s
end
last_value = v
end
I basically have an array of objects (all the same type) and need to join their #name attributes, but following an object that has a particular characteristic, I need a different separator.
This is an easily solved problem, but I'm looking for the cleanest, most functional approach. I first dived into #inject, but you lose the previous value at each iteration, so I couldn't make that work. Any ideas?
I'd love to post the real code instead of pseudo code, but it's really dense and complex DataMapper Relationship stuff, so you probably couldn't just run it anyway, sorry :(
If I understand correctly what you want, this should work:
output = values.map do |v|
["/" + v.name.to_s, v.special? ? "/x" : ""]
end.flatten[0...-1].join
Alternative phrasing (Ruby 1.9):
output = "/" + values.flat_map do |v|
[v.name.to_s, ("x" if v.special?)]
end.take(2*values.size - 1).join("/")
Without analyzing the algorithm, just making it functional:
output = ([nil] + values).each_cons(2).map do |last_value, v|
if last_value && last_value.special?
"/x/" + v.name.to_s
else
"/" + v.name.to_s
end
end.join
try values.collect{|v| v.special? ? v + "/x/" : v + "/"}.join("")
EDIT, solution using inject:
values.inject(["", ""]) {|path_and_sep, item| [path_and_sep[0] + path_and_sep[1] + item, item.special? "/x/" : "/"]} [0]
Do the join at the end to get rid of the leading and trailing /:
values.collect{|v| v.special? && v != values.last ? [v.name.to_s, "x"] : v.name.to_s}.flatten.join("/")
I'm not particularly proud of that one, but it's kind of functional ;)
values.clone.unshift(nil).each_cons(2).map { |last_value, v|
last_value.special? ? "/x/" + v.to_s : "/" + v.to_s
}.join()
The clone is needed because each_cons destroys he original array.
values.map{|x| x.special? ? [x, SEPARATOR_2] : [x, SEPARATOR_1]}.flatten[0..-2].join('')
values.inject('') { |m, e| m << e.to_s; m << (e.special? ? '/x/' : '/' }
To make it completely functional you could recreate m each time instead of appending to it.
And to avoid the last iteration, perhaps something more complex like:
delay = ''
values.inject('') do |m, e|
m << delay << e.to_s
delay = e.special? ? '/x/' : '/'
m
end

Recursively merge multidimensional arrays, hashes and symbols

I need a chunk of Ruby code to combine an array of contents like such:
[{:dim_location=>[{:dim_city=>:dim_state}]},
:dim_marital_status,
{:dim_location=>[:dim_zip, :dim_business]}]
into:
[{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]},
:dim_marital_status]
It needs to support an arbitrary level of depth, though the depth will rarely be beyond 8 levels deep.
Revised after comment:
source = [{:dim_location=>[{:dim_city=>:dim_state}]}, :dim_marital_status, {:dim_location=>[:dim_zip, :dim_business]}]
expected = [{:dim_location => [:dim_business, {:dim_city=>:dim_state}, :dim_zip]}, :dim_marital_status]
source2 = [{:dim_location=>{:dim_city=>:dim_state}}, {:dim_location=>:dim_city}]
def merge_dim_locations(array)
return array unless array.is_a?(Array)
values = array.dup
dim_locations = values.select {|x| x.is_a?(Hash) && x.has_key?(:dim_location)}
old_index = values.index(dim_locations[0]) unless dim_locations.empty?
merged = dim_locations.inject({}) do |memo, obj|
values.delete(obj)
x = merge_dim_locations(obj[:dim_location])
if x.is_a?(Array)
memo[:dim_location] = (memo[:dim_location] || []) + x
else
memo[:dim_location] ||= []
memo[:dim_location] << x
end
memo
end
unless merged.empty?
values.insert(old_index, merged)
end
values
end
puts "source1:"
puts source.inspect
puts "result1:"
puts merge_dim_locations(source).inspect
puts "expected1:"
puts expected.inspect
puts "\nsource2:"
puts source2.inspect
puts "result2:"
puts merge_dim_locations(source2).inspect
I don't think there's enough detail in your question to give you a complete answer, but this might get you started:
class Hash
def recursive_merge!(other)
other.keys.each do |k|
if self[k].is_a?(Array) && other[k].is_a?(Array)
self[k] += other[k]
elsif self[k].is_a?(Hash) && other[k].is_a?(Hash)
self[k].recursive_merge!(other[k])
else
self[k] = other[k]
end
end
self
end
end

Resources