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

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

Related

Stringifying query parameters: can we go further?

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

How to convert a ruby lambda to a reusable library method

I have a method in a chef recipe like so
overwrite_properties = lambda do |tmpl_path, params|
attrs = {}
File.read(tmpl_path).split("\n").map do |line|
line = line.sub(/#.*$/, '').strip
j, v = line.split(/\s*=\s*/, 2)
attrs[j] = v if j
end
params.each {|j,v| v.nil? ? attrs.delete(j) : attrs[j] = v }
attrs.map {|j,v| "#{j}=#{v}\n" }.sort.join
end
which is called like so
overwrite_properties.call("#{server_home}/config.orig/#{fname}", params)
My question is how can I convert this to a reusable function to be called from another module?
eg.
module HelperMod
def self.overwrite_properties(&block)
//etc
end
end
Thanks
This is simple - a lambda is an anonymous function (function with no name). You can convert this to a function within a module like so:
module HelperMod
def self.overwrite_properties(tmpl_path, params)
attrs = {}
File.read(tmpl_path).split("\n").map do |line|
line = line.sub(/#.*$/, '').strip
j, v = line.split(/\s*=\s*/, 2)
attrs[j] = v if j
end
params.each {|j,v| v.nil? ? attrs.delete(j) : attrs[j] = v }
attrs.map {|j,v| "#{j}=#{v}\n" }.sort.join
end
end
HelperMod.overwrite_properties("#{server_home}/config.orig/#{fname}", params)

Check if two linked lists are equal in Ruby?

I have the following implementation of a linked list in Ruby:
class Node
attr_accessor :data, :next
def initialize(data = nil)
#data = data
#next = nil
end
end
class LinkedList
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def iterate
return nil if #head.nil?
entry = #head
until entry.nil?
yield entry
entry = entry.next
end
end
def equal?(other_list)
#How do I check if all the data for all the elements in one list are the same in the other one?
end
end
I have tried using the .iterate like this:
def equals?(other_list)
other_list.iterate do |ol|
self.iterate do |sl|
if ol.data != sl.data
return false
end
end
end
return true
end
But this is doing a nested approach. I fail to see how to do it.
You can't do it easily with the methods you have defined currently, as there is no way to access a single next element. Also, it would be extremely useful if you implemented each instead of iterate, which then gives you the whole power of the Enumerable mixin.
class LinkedList
include Enumerable # THIS allows you to use `zip` :)
class Node # THIS because you didn't give us your Node
attr_accessor :next, :value
def initialize(value)
#value = value
#next = nil
end
end
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def each
return enum_for(__method__) unless block_given? # THIS allows block or blockless calls
return if #head.nil?
entry = #head
until entry.nil?
yield entry.value # THIS yields node values instead of nodes
entry = entry.next
end
end
def ==(other_list)
# and finally THIS - get pairs from self and other, and make sure all are equal
zip(other_list).all? { |a, b| a == b }
end
end
a = LinkedList.new([1, 2, 3])
b = LinkedList.new([1, 2, 3])
c = LinkedList.new([1, 2])
puts a == b # => true
puts a == c # => false
EDIT: I missed this on the first run through: equal? is supposed to be referential identity, i.e. two variables are equal? if they contain the reference to the same object. You should not redefine that method, even though it is possible. Rather, == is the general common-language meaning of "equal" as in "having the same value", so I changed it to that.
I think there is something wrong with your initialize method in LinkedList, regardless could this be what you need
...
def equal?(other_list)
other_index = 0
cur_index = 0
hash = Hash.new
other_list.iterate do |ol|
hash[ol.data.data] = other_index
other_index += 1
end
self.iterate do |node|
return false if hash[node.data.data] != cur_index
return false if !hash.has_key?(node.data.data)
cur_index += 1
end
return true
end
...
Assuming this is how you use your code
a = Node.new(1)
b = Node.new(2)
c = Node.new(3)
listA = [a,b,c]
aa = Node.new(1)
bb = Node.new(2)
cc = Node.new(3)
listB = [aa,bb,cc]
linkA = LinkedList.new(listA)
linkB = LinkedList.new(listB)
puts linkA.equal?(linkB)

Is there an abstraction for the declare-update-return pattern?

When writing iterative code with mutation in ruby, I often find myself following this pattern:
def build_x some_data
x = [] # or x = {}
some_data.each do |data|
x.some_in_place_update! (... data ...)
end
x
end
(x often does not have the same shape as some_data, so a simple map will not do.)
Is there a more idiomatic or better way to write code that follows this pattern?
[edit] A real example:
def to_hierarchy stuff
h = {}
stuff.each do |thing|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
h
end
This begins with a flat list of things, which have related but distinct uris. It transforms this flat list into a hierarchy, grouping related things that share the same segments of a uri. This follows the pattern I described: initialize h, loop over some data and mutate h along the way, and then spit out h at the end.
[edit2] Another related example
def count_data obj
i = if obj[:data] then 1 else 0
obj.each do |k, v|
i += count_statements v unless :data == k
end
i
end
Your to_hierarchy example could be done with each_with_object:
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
#...
end
end
each_with_object passes the extra object to the block and returns that object when the iteration is done.
If you're more of a traditionalist, you could use inject:
def to_hierarchy stuff
stuff.inject({}) do |h, thing|
#...
h
end
end
Note the block argument order change and that the block has to return h so that inject can feed it back into the next block invocation.
Your general example could be written as:
def build_x some_data
some_data.each_with_object([]) do |data, x|
x.some_in_place_update! (... data ...)
end
end
or:
def build_x some_data
some_data.inject({}) do |x, data|
x.some_in_place_update! (... data ...)
x
end
end
Ah! You want each_with_object. Like this
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
end

How to handle combination []+= for auto-vivifying hash in Ruby?

In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }

Resources