XHTML to multidimensional hash in Ruby - 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"]
# }
# }

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] } }

How to organize hashes by property

Here is the hashes that will be processed:
{
"flatiron school bk" => {
:location => "NYC"
},
"flatiron school" => {
:location => "NYC"
},
"dev boot camp" => {
:location => "SF"
},
"dev boot camp chicago" => {
:location => "Chicago"
},
"general assembly" => {
:location => "NYC"
},
"Hack Reactor" => {
:location => "SF"
}
}
I need to organize these hashes by location, like this:
{ "NYC"=>["flatiron school bk", "flatiron school", "general assembly"],
"SF"=>["dev boot camp", "Hack Reactor"],
"Chicago"=>["dev boot camp chicago"]}
}
You can use each_with_object to combine in into new hash:
hash.each_with_object({}) do |(name, data), res|
(res[data[:location]] ||= []) << name
end
Explanation:
each_with_object
Iterates the given block for each element with an arbitrary object given, and returns the initially given object.
In this case name and data is key and value of each element in given hash.
In (res[data[:location]] ||= []) << name you get location, create array in result hash for given location (if it doesn't exist), then put key of input hash to it.

Update activerecord relation given a hash of multiple entries

I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!
So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)

How to validate a complete form with RSpec and Capybara?

I'm writing a request test with RSpec and Capybara. I have a hash that maps form field names to expected values.
How can I check easily that each form field has the expected value?
So far, I'm doing this, but it's complex and unmaintainable. I'm also considering only two kind of input controls in this case (select boxes and the rest):
expected_data = {
"address" => "Fake st 123",
"city" => "Somewhere",
"email" => "whoknows#example.com",
"gender" => "Male",
"state" => "FL",
}
select_boxes = ["gender", "state"]
# check for the select boxes
expected_data.select {|k,v| select_boxes.include?(k)}.each do |name, expected_value|
page.has_select?(name, :selected_value => expected_value).should == true
end
# check for the input fields
expected_data.reject {|k,v| select_boxes.include?(k)}.values.each do |expected_value|
page.should have_css("input[value=\"#{expected_value}\"]")
end
Is there a gem or something to do this in one line?
I find the following far more maintainable:
describe "form" do
subject {page}
before { visit "/path/to/form" }
it { should have_field("address", :with => "Fake st 123") }
it { should have_select("gender", :selected => "Male") }
# And so on ...
end

Link_to with additional variable

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? }

Resources