Link_to with additional variable - ruby

I want to create a simple link_to (rails 3) with two additional variables:
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2}, :proof => true)
The problem is if users2 is blank, this html code is generated: &k=[1]&&proof=true
I tried something like this. Can you help me please?
= link_to 'Try', new_try_path(:k => users.collect{|m| m.user.username}, :h=> users2.collect{|m| m.user2.username2} if users2.blank?, :proof => true)
Thank you!

Things like this should definitely be refactored into a helper, such as
# view
= try_link(users, users2)
# helper
def try_link(users, users2)
options = { :k => users.collect { |m| m.user.username }, :proof => true }
unless users2.blank?
options[:h] = users2.collect { |m| m.user2.username2 }
end
link_to 'Try', new_try_path(options)
end
This is about the bare minimum you can do to make the view code less horrible.
You might also want to consider putting the whole collect thing into the model.
Also Hash#merge might be helpful in cases like this, where you can do
a = { :foo => 1 }
b = { :bar => 2 }
puts a.merge(b) # => { :foo => 1, :bar => 2 }

Not very elegant, but should work:
- options = { :k => users.map{ |m| m.user.username }, :proof => true }
-# add :h parameter only if users2 is not empty
- options[:h] = users2.map{ |m| m.user2.username2 } unless users2.blank?
= link_to 'Try, new_try_path(options)
If users2 is blank h parameter will be omitted from generated URL.
As alternative you can filter out blank values from options hash:
# for ruby 1.9 (select only non-blank values)
options.select! { |k, v| v.present? }
# for ruby 1.8 (delete blank values)
options.delete_if { |k, v| v.blank? }

Related

Ruby: transform Hash-Keys

I have a Hash:
urls = [{'logs' => 'foo'},{'notifications' => 'bar'}]
The goal is to add a prefix to the keys:
urls = [{'example.com/logs' => 'foo'},{'example.com/notifications' => 'bar'}]
My attempt:
urls.map {|e| e.keys.map { |k| "example.com#{k}" }}
Then I get an array with the desired form of the keys but how can I manipulate the original hash?
If you want to "manually" transform the keys, then you can first iterate over your array of hashes, and then over each object (each hash) map their value to a hash where the key is interpolated with "example.com/", and the value remains the same:
urls.flat_map { |hash| hash.map { |key, value| { "example.com/#{key}" => value } } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
Notice urls are being "flat-mapped", otherwise you'd get an arrays of arrays containing hash/es.
If you prefer to simplify that, you can use the built-in method for for transforming the keys in a hash that Ruby has; Hash#transform_keys:
urls.map { |url| url.transform_keys { |key| "example.com/#{key}" } }
# [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
Use transform_keys.
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.map { |hash| hash.transform_keys { |key| "example.com/#{key}" } }
# => [{"example.com/logs"=>"foo"}, {"example.com/notifications"=>"bar"}]
One question: are you best served with an array of hashes here, or would a single hash suit better? For example:
urls = { 'logs' => 'foo', 'notifications' => 'bar' }
Seems a little more sensible a way to store the data. Then, saying you did still need to transform these:
urls.transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Or to get from your original array to the hash output:
urls = [{'logs' => 'foo'}, {'notifications' => 'bar'}]
urls.reduce({}, &:merge).transform_keys { |key| "example.com/#{key}" }
# => {"example.com/logs"=>"foo", "example.com/notifications"=>"bar"}
Much easier to work with IMHO :)
If you don't have access to Hash#transform_keys i.e. Ruby < 2.5.5 this should work:
urls.map{ |h| a = h.to_a; { 'example.com/' + a[0][0] => a[0][1] } }

Nested json from ruby hash

Trying to build a json out of Ruby hash
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each do |key, value|
temp_rides["rides"].push({"key" => "value"})
puts temp_rides
end
apparently it fails with undefined methodpush'` I believe I need to load the json object before append (based on my python background).
I am looking for a output something like this
{
"rides": {
"lyft": {
"type": "car"
},
"scoot": {
"type": "scooter"
},
"blade": {
"type": "helicopter"
}
}
}
This is an answer to the OP's original question. I have no interest in changing it to attempt to answer a moving target.
Code
def doit(rides)
{ rides: rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }.
map { |a| hashify(a.flatten.drop(1)) }.
reduce(&:merge) }
end
def hashify(arr)
f,*rest = arr
return f if rest.empty?
{ f.to_sym=>hashify(rest) }
end
Examples
Here is an example of the use of the (recursive) helper method hashify:
hashify ["lyft", :type, "car"]
#=> {:lyft=>{:type=>"car"}}
We are given the hash rides:
rides = {:ride1=>"lyft", :type=>"car", :ride2=>"Scoot",
:type2=>"scooter", :ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>"car"},
# :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}}
Let's add some more key-value pairs to rides:
rides = {:ride1=>"lyft", :type=>"car", :color=>"blue",
:ride2=>"Scoot", :type2=>"scooter", :make=>"Vespa", :model=>"98",
:ride3=>"blade", :type3=>"helicopter"}
doit rides
#=> {:rides=>{:lyft=>{:type=>{:car=>{:color=>"blue"}}},
# :Scoot=>{:type2=>{:scooter=>{:make=>
# {:Vespa=>{:model=>"98"}}}}},
# :blade=>{:type3=>"helicopter"}}}
Explanation
The steps for the first example are as follows.
enum = rides.slice_before { |k,_| k.to_s.match? /\Aride\d*\z/ }
#=> #<Enumerator: #<Enumerator::Generator:0x00005a49d68217f0>:each>
We can see the elements that will be generated by this enumerator by converting it to an array.
enum.to_a
#=> [[[:ride1, "lyft"], [:type, "car"]],
# [[:ride2, "Scoot"], [:type2, "scooter"]],
# [[:ride3, "blade"], [:type3, "helicopter"]]]
Continuing,
a = enum.map { |a| hashify(a.flatten.drop(1)) }
#=> [{:lyft=>{:type=>"car"}},
# {:Scoot=>{:type2=>"scooter"}},
# {:blade=>{:type3=>"helicopter"}}]
h = a.reduce(&:merge)
#=> {:lyft=>{:type=>"car"}, :Scoot=>{:type2=>"scooter"},
# :blade=>{:type3=>"helicopter"}}
{ rides: h }
#=> <as above>
It gave you undefined method push because there is no such method for a hash.
temp_rides = {"rides": {}}
# This create a hash with `:rides` symbol as the key {:rides=>{}}
# To push an object into a hash. Use operator[]= or #store method
temp_rides[:rides][:key1] = 'value1'
temp_rides[:rides].store(:key2, 'value2')
A working example:
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
rides.each_pair do |k, v|
temp_rides[:rides][k] = {:type => v}
end
puts JSON.pretty_generate(temp_rides)
This is actually pretty easy to do with a simple transform:
def rejig(rides)
rides.map do |name, type|
[ name, { type: type } ]
end.to_h
end
Where that will rework your structure into the desired shape.
Using it is easy:
require 'json'
rides = { lyft: "car", scoot: "scooter", blade: "helicopter" }
puts JSON.dump(rejig(rides))
# => {"lyft":{"type":"car"},"scoot":{"type":"scooter"},"blade":{"type":"helicopter"}}
require 'json'
temp_rides = {"rides": {}}
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
temp_rides[:rides].merge!(rides)
to get ruby hash output
temp_rides
to get json format output
JSON.dump temp_rides
require 'json'
rides = {:lyft => "car", :scoot => "scooter", :blade => "helicopter"}
transformed = {
'rides' => rides.inject({}) { |h,(k,v)| h[k] = { 'type' => v }; h }
}
JSON.dump(transformed)

How to loop inside a hash?

If I have a hash like the one below, and I want to loop over the second level keys.
Why does this fail?
hash["Element"].each do |id|
h[id] = hash[id]["Name"]
end
hash
{
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
It fails because using .each on a Hash yields the tuple of key and value.
hash = {
"Element" => {
"499723" => {
"Name" => "A",
},
"499725" => {
"Name" => "B",
},
}
}
hash["Element"].each do |id|
p id
end
["499723", {"Name"=>"A"}]
["499725", {"Name"=>"B"}]
Therefore, you need to use
hash["Element"].each do |id, value|
# ...
end
If you don't need the value
hash["Element"].each do |id, _|
# ...
end
However, you can keep the value and access it directly
hash["Element"].each do |id, value|
h[id] = value["Name"]
end
A simple solution in your case is to use Enumberable#each_with_object in combination with the previous information:
hash["Element"].each_with_object({}) do |(id, value), acc|
acc[id] = value["Name"]
end
# => {"499723"=>"A", "499725"=>"B"}
hash["Element"].each.with_object({}) do |(id, subhash), result|
result[id] = subhash["Name"]
end

ruby building hash from url

In Ruby, what is an efficient way to construct a Hash from a request path like:
/1/resource/23/subresource/34
into a hash that looks like this:
{'1' => { 'resource' => { '23' => 'subresource' => { '34' => {} } } }
Thanks
path = "/1/resource/23/subresource/34"
path.scan(/[^\/]+/).inject(hash = {}) { |h,e| h[e] = {} }
hash
=> {"1"=>{"resource"=>{"23"=>{"subresource"=>{"34"=>{}}}}}}
A recursive solution seems like the simplest thing to do. This isn't the prettiest, but it works:
def hashify(string)
k,v = string.gsub(/^\//, '').split('/', 2)
{ k => v.nil? ? {} : hashify(v) }
end
There may be edge cases it doesn't handle correctly (probably are) but it satisfies the example you've given.

XHTML to multidimensional hash in Ruby

I've been looking around for libraries that will allow me to get a multidimensional hash of a given XHTML string.
XHTML:
<div class="class-1 class-2" id="my-id">
<div class="classy">
</div>
</div>
Expected Hash:
hash = {
:div => {
:class => ['class-1', 'class-2'],
:id => ['my-id'],
:children => {
:div => {
:class => ['classy']
}
}
}
}
Your example does not really give a well defined definition of what should be returned. Are text nodes ignored? What happens if an element has multiple <div> child elements? What happens if the outer <div> element has an attribute named children?
In addition to that, you probably shouldn't build a structure like this if you have a way of using the built-in data structure of the XML/HTML parsing library of your choice, and using XPath queries to arrive at the data nodes you want.
Disregarding all of the above, here is a simple start that may come close to what you have in mind.
require "nokogiri"
class Nokogiri::XML::Node
def to_hash
# Build hash of attributes. Attribute values are split into arrays.
contents = Hash[attributes.collect { |name, value|
[name.to_sym, value.to_s.split(/\s+/)] }]
# Add array of child hashes recursively.
if element_children.any?
contents[:children] = element_children.collect { |child| child.to_hash }
end
# Return new hash with the element name as single key.
{ name.to_sym => contents }
end
end
Use as follows:
doc = Nokogiri::XML('<div class="class-1 class-2" id="my-id">
<div class="classy">
</div>
</div>')
doc.root.to_hash
#=> { :div =>
# { :class => ["class-1", "class-2"],
# :children =>
# [ { :div =>
# { :class => ["classy"] }
# } ],
# :id => ["my-id"]
# }
# }

Resources