Ruby - loop through array - ruby

In a form I have a list of products from database and every product has these items in the form:
= text_field_tag 'item['+product.id.to_s+'][]'
= text_field_tag 'item_value1['+product.id.to_s+'][]'
= text_field_tag 'item_value2['+product.id.to_s+'][]'
I am trying to loop over the array and get all those (item, item_value1, item_value2) this way:
params[:item].each_with_index do |val, index|
puts "#{val[0]} => #{val[1]}"
end
and the output is like:
191359 => [""]
191361 => [""]
191360 => ["15"]
191212 => [""]
191210 => ["9"]
248974 => [""]
191209 => [""]
190920 => [""]
190919 => [""]
190921 => [""]
But, how to get all data for the respective products? Something like
puts "item: #{item}, item_value1: #{item_value1}, item_value2: #{item_value2}"

There are three parameters here item, item_value1 and item_value2. Iterating over item will give you values from parameter items only and not from intem_value1 and item_value2. If the indexes of these 3 parameters are relative, then you can play with index as in your code
params[:item].each_with_index do |val, index|
puts "#{params[:item_value1][index]} => #{params[:item_value1][index]}"
end

Related

How would I construct a Hash from this scenario in Ruby?

Given I have the following code:
ENDPOINT = 'http://api.eventful.com'
API_KEY = 'PbFVZfjTXJQWrnJp'
def get_xml(url, options={})
compiled_url = "#{ENDPOINT}/rest#{url}" << "?app_key=#{API_KEY}&sort_order=popularity"
options.each { |k, v| compiled_url << "&#{k.to_s}=#{v.to_s}" }
REXML::Document.new((Net::HTTP.get(URI.parse(URI.escape(compiled_url)))))
end
def event_search(location, date)
get_xml('/events/search',
:location => "#{location}, United Kingdom",
:date => date
)
end
And we access the XML data formatted by REXML::Document like this:
events = event_search('London', 'Today').elements
And we can access these elements like this (this prints all the titles in the events):
events.each('search/events/event/title') do |title|
puts title.text
end
The XML I'm using can be found here. I would like this construct a Hash like so:
{"Title1" => {:title => 'Title1', :date => 'Date1', :post_code => 'PostCode1'},
"Title2" => {:title => 'Title2', :date => 'Date2', :post_code => 'PostCode2'}}
When using events.each('search/events/event/title'), events.each('search/events/event/date'), and events.each('search/events/event/post_code').
So I want to create a Hash from the XML provided by the URL I have included above. Thanks!
You should loop over the events themselves, not the titles. Something like this
events_by_title = {}
elements.each('search/events/event') do |event|
title = event.get_elements('title').first.text
events_by_title[title] = {
:title => title,
:date => event.get_elements('start_time').first.text
:post_code => event.get_elements('postal_code').first.text,
}
end
Get the root element using root() on the REXML:Document object then use each_element("search/events/event") to iterate over "event" node. You can then extract the different values out of it using the different methods on element: http://ruby-doc.org/stdlib-1.9.3/libdoc/rexml/rdoc/REXML/Element.html

How to pass a hash object to a HAML tag

Please consider this example:
- user_links_params = _user_link_params(current_user)
%a{ :'data-msgstore-path' => user_links_params[:'data-msgstore-path'],
:'data-user_id' => user_links_params[:'data-user_id'],
:class => user_links_params[:class],
}
/ too many html tags and stuff to fit in a simple link_to
I would be nice to fit all this in a simple statement like the following:
%a[_user_link_params(current_user)]
/ too many html tags and stuff to fit in a simple link_to
Yes, it's possible, and you were close:
%a{_user_link_params(current_user)}
From the HAML reference:
For example, if you defined
def hash1
{:bread => 'white', :filling => 'peanut butter and jelly'}
end
def hash2
{:bread => 'whole wheat'}
end
then %sandwich{hash1, hash2, :delicious => true}/ would compile to:
<sandwich bread='whole wheat' delicious='true' filling='peanut butter and jelly' />

Converting OpenStruct/Hash to XML

I have a collection of OpenStruct elements using which I need to build an XML with help of Nokogiri.
collection = [
OpenStruct.new(:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates',
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(
:productType => 'Book',
:productName => 'The Client',
:productAuthorized => 'Y',
:productId => 'BKSUS113246A',
:productVerificationCode => '4546747',
:productPurcTransactionTime => '2012-05-21T13:36:38+05:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'StoreUserS14',
:productAuditTime => '2012-05-21T13:36:38+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '50.14 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
),
:productAuditDetails => OpenStruct.new(
:productAuditNo => '2',
:prodHandledByUser => 'OnlineUserOn008',
:productAuditTime => '2012-05-23T16:16:08+05:30',
:productAuditAdminId => 'McGr1132',
:productPurchaseRate => '84.86 Prcnt',
:productSystemLoggerId => 'UNX-NETW4536'
)
),
:catalogProductInfo => OpenStruct.new(
:productType => 'Pen',
:productName => 'Reynolds'
:productAuthorized => 'N',
:productId => 'PNSUS228886B',
:productVerificationCode => '2330076',
:productPurcTransactionTime => '2012-04-22T15:06:18+04:30',
:productAuditDetails => OpenStruct.new(
:productAuditNo => '1',
:prodHandledByUser => 'CCUserA14',
:productAuditTime => '2012-04-26T13:36:38+05:30',
:productAuditAdminId => 'ReyGr1132',
:productPurchaseRate => '20.19 Prcnt',
:productSystemLoggerId => 'WIN-NETW4536'
)
)
)
)]
I tried with below code .. as per your answer (handpick of elements)
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
collection.each do |ctlg|
xml.CatalogStoreNumber ctlg.catalogStoreNumber
xml CatalogStoreId ctlg.catalogStoreId
xml.CatalogOwnerId ctlg.catalogOwnerid
xml.CatalogOwner ctlg.catalogOwner
xml.CatalogList do
prod_count = 0
aud_list_count = 0
collection.each do |prod|
info = prod.catalogList[0].catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId info.productId
xml.ProductVerificationCode info.ProductVerificationCode
xml.ProductPurcTransactionTime info.productPurcTransactionTime
xml.ProductAuditDetails do
collection.each do |aud_dtl|
aud_info = aud_dtl.catalogList[0].catalogProductinfo[0].productAuditDetails
xml.ProductAuditNo aud_info.productAuditNo
xml.ProdHandledByUser aud_info.prodHandledByUser
xml.ProductAuditTime aud_info.productAuditTime
xml.ProductAuditAdminId aud_info.productAuditAdminId
xml.ProductPurchaseRate aud_info.productPurchaseRate
xml.ProductSystemLoggerId aud_info.productSystemLoggerId
# Do whatever you must above to concoct your ProductId
end
aud_list_count = aud_list_count + 1
end
prod_count = prod_count + 1
end
end
end
puts builder.to_xml
I need the Output as below...
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogStoreLocation>UnitedStates</CatalogStoreLocation>
<CatalogOwnerId>TYCT11190</CatalogOwnerId>
<CatalogOwner>McGrawHill Pub.</CatalogOwner>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductAuthorized>Y</ProductAuthorized>
<ProductId>BKSUS113246A</ProductId>
<ProductVerificationCode>4546747</ProductVerificationCode>
<ProductPurcTransactionTime>2012-05-21T13:36:38+05:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
<ProductAuditTime>2012-05-21T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>50.14 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
<ProductAuditTime>2012-05-23T16:16:08+05:30</ProductAuditTime>
<ProductAuditAdminId>McGr1132</ProductAuditAdminId>
<ProductPurchaseRate>84.86 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>UNX-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuthorized>N</ProductAuthorized>
<ProductId>PNSUS228886B</ProductId>
<ProductVerificationCode>2330076</ProductVerificationCode>
<ProductPurcTransactionTime>2012-04-22T15:06:18+04:30</ProductPurcTransactionTime>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
<ProductAuditTime>2012-04-26T13:36:38+05:30</ProductAuditTime>
<ProductAuditAdminId>ReyGr1132</ProductAuditAdminId>
<ProductPurchaseRate>20.19 Prcnt</ProductPurchaseRate>
<ProductSystemLoggerId>WIN-NETW4536</ProductSystemLoggerId>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>
I tried to loop in the nested Array of OpenStruct of elements, but couldn't land on right logic for that ..
Ref.. How to add child nodes in NodeSet using Nokogiri
Setup Code
require 'ostruct'
require 'nokogiri'
collection = [
OpenStruct.new(
:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates',
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(
:productType => 'Book',
:productName => 'The Client'
)
)
)
]
If You Want To Hand-Pick Your Elements and Data
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod|
info = prod.catalogList.catalogProductInfo
xml.ProductInfo do
xml.ProductType info.productType
xml.ProductName info.productName
xml.ProductId "#{prod.catalogOwnerId}-#{prod.catalogStoreNumber}"
# Do whatever you must above to concoct your ProductId
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductId>TYCT11190-657758</ProductId>
</ProductInfo>
</CatalogList>
</CatalogOrder>
If you want a more generic conversion (an XML representation of your OpenStruct hierarchy) see either of the two solutions below:
One Way to Perform Generic Conversion
# Add all entries of an OpenStruct to an XML builder
# Recursively creates sub-nodes for OpenStruct instances
def ostruct_each(ostruct,xml)
ostruct.instance_variable_get(:#table).each do |field,value|
if value.is_a?(OpenStruct)
xml.send(field) do
ostruct_each(value,xml)
end
else
xml.send(field,value)
end
end
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
ostruct_each(prod_info,xml)
end
end
end
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogList>
<ProductInfo>
<catalogStoreNumber>657758</catalogStoreNumber>
<catalogStoreId>CTH6536</catalogStoreId>
<catalogStoreLocation>UnitedStates</catalogStoreLocation>
<catalogOwnerId>TYCT11190</catalogOwnerId>
<catalogOwner>McGrawHill Pub.</catalogOwner>
<catalogList>
<catalogProductInfo>
<productType>Book</productType>
<productName>The Client</productName>
</catalogProductInfo>
</catalogList>
</ProductInfo>
</CatalogList>
</CatalogOrder>
A Different Way to Generically Convert It
# Create a NodeSet of elements for all attributes in an OpenStruct
# Recursively creates child elements for any value that is an OpenStruct
def ostruct_to_elements(xml_doc,ostruct)
Nokogiri::XML::NodeSet.new(
xml_doc,
ostruct.instance_variable_get(:#table).map do |name,val|
xml_doc.create_element(name.to_s).tap do |el|
el << (val.is_a?(OpenStruct) ? ostruct_to_elements(xml_doc,val) : val)
end
end
)
end
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
xml.CatalogList do
collection.each do |prod_info|
xml.ProductInfo do
xml.parent << ostruct_to_elements(xml.doc,prod_info)
end
end
end
end
end
puts builder.to_xml
Answering your changed question and data.
Here's your source data, summarized:
collection = [
OpenStruct.new(
:foo => 'bar',
:list => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(...)
:catalogProductInfo => OpenStruct.new(...)
)
)
]
The first thing we notice is that collection is an array, but it only has one item. That doesn't seem very useful.
The second—far more important—thing to notice is that you are attempting to use an OpenStruct (the inner one) as an array. This fails quite fully, as the second value completely overwrites the first:
require 'ostruct'
p OpenStruct.new( a:1, a:2 )
#=> #<OpenStruct a=2>
You can't have two values for the same key in a struct. Instead, you probably wanted an array value inside your object. For example:
root = OpenStruct.new(
:foo => 'bar',
:list => [ # this is an array of two distinct objects
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) ),
OpenStruct.new( :catalogProductInfo => OpenStruct.new(...) )
]
)
Thirdly, as I noted earlier, I see no good reason for you to be using an OpenStruct here. Instead, just use Hash literals. Combined with the code I provided in my other answer, this is what your data (simplified) and a working solution looks like:
Source Data
MyOrder = {
catalogStoreNumber: '657758', # Ruby 1.9 Hash syntax;
catalogStoreId: 'CTH6536', # same as :catalogStoreId => 'CTH536'
catalogList: {
catalogProductInfo: [
{
productType: 'Book',
productName: 'The Client',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'StoreUserS14' },
{ productAuditNo: '2', prodHandledByUser: 'OnlineUserOn008' }
]
},
{
productType: 'Pen',
productName: 'Reynolds',
productAuditDetails: [
{ productAuditNo: '1', prodHandledByUser: 'CCUserA14' }
]
}
]
}
}
Generic Conversion
# Adds key/value pairs from a Hash to a Nokogiri::XML::Builder
def hash2xml(hash,xml)
hash.each do |field,value|
name = field.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar"
case value
when Hash then xml.send(name){ hash2xml(value,xml) }
when Array then value.each{ |o| xml.send(name){ hash2xml(o,xml) } }
else xml.send(name,value)
end
end
end
Working Code
builder = Nokogiri::XML::Builder.new do |xml|
xml.CatalogOrder do
hash2xml(MyOrder,xml)
end
end
puts builder.to_xml
Output
<?xml version="1.0"?>
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>StoreUserS14</ProdHandledByUser>
</ProductAuditDetails>
<ProductAuditDetails>
<ProductAuditNo>2</ProductAuditNo>
<ProdHandledByUser>OnlineUserOn008</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductAuditDetails>
<ProductAuditNo>1</ProductAuditNo>
<ProdHandledByUser>CCUserA14</ProdHandledByUser>
</ProductAuditDetails>
</CatalogProductInfo>
</CatalogList>
</CatalogOrder>

Ruby: Loop through hash and check if a key exists to determine markup and data to be displayed

This is my first time working with Ruby, so I may be approaching this incorrectly.
I am trying to go through a hash to display it's contents. As I'm going through the hash I'll need to test if a key exists, like city. If city doesn't exist then it shouldn't display the address. This is where I've started with building my hash:
# app.rb
set :haml, :format => :html5
get "/" do
#users = Hash[
[["name", "bill"], ["city", "nyc"], ["address", "street"]],
[["name", "ted"], ["city", "denver"]],
[["name", "sam"], ["address", "road"]]
]
haml :index
end
And this is how I am looping through the hash:
# layout.haml
- #users.each do |user|
- user.each do |u|
- u.each do |b|
= b
Once I get to b it will display all of the content like so:
["name", "bill"]
["city", "nyc"]
["address", "street"]
["name", "ted"]
["city", "denver"]
In the loop, how can I display the name as well as check to see if the address exists for each user to determine if the city should be displayed as well as any markup that may need to be added? It would ideally display something like:
<p>bill, <span class="address">nyc, street</span></p>
<p>ted</p>
<p>sam, <span class="address">road</span></p>
Am I creating the Hash properly to do it this way?
Instead of what you are trying to do with nested arrays inside a hash, it would be better to have an array that contains user hashes:
#users = [
{ :name => 'bill', :city => 'city', :address => 'street' },
{ :name => 'ted', :city => 'denver' },
{ :name => 'sam', :address => 'road' }
]
With that, you can do something like this:
- #users.each do |user|
= user[:name]
- if user.has_key?(:address) && user.has_key?(:city)
= "#{user[:address]}, #{user[:city]}"
- elsif user.has_key?(:address)
= "#{user[:address]}"

How can I get all the checked items from a submitted form with sinatra's params?

I'm running Sinatra 1.0 with HAML, my form has a number of checkboxes, for example books I like, and you would select all the books you want. The checkbox name is "books".
In sinatra params['books'] there should be an array of all the books that were checked, but it only has the last item that was checked, not an array.
How can I get all the checked items?
HAML:
%form{:action => "/test", :method => 'post'}
%input{:name=>'check',:type=>'checkbox',:value=>'item1'} item 1
%input{:name=>'check',:type=>'checkbox',:value=>'item2'} item 2
%input{:name=>'check',:type=>'checkbox',:value=>'item3'} item 3
%input{:type => "submit", :value => "send", :class => "button"}
Sinatra get method
post '/test' do
puts params['check'] #should be an array but is last item checked
end
Very close, but do not but numbers in the arrays
%form{:action => "/test", :method => 'post'}
%input{:name=>'check[]',:type=>'checkbox',:value=>'item1'} item 1
%input{:name=>'check[]',:type=>'checkbox',:value=>'item2'} item 2
%input{:name=>'check[]',:type=>'checkbox',:value=>'item3'} item 3
Now,
post '/test' do
puts params['check'] #puts an array of what was checked to stdout
end
Wouldn't that output a bunch of checkboxes with the same name? If so, params['check'] is probably getting replaced with each new checkbox.
Try naming each one something different. If you really want it in an array, try hacking the names:
%input{:name=>'check[1]',:type=>'checkbox',:value=>'item1'} item 1
%input{:name=>'check[2]',:type=>'checkbox',:value=>'item2'} item 2
%input{:name=>'check[3]',:type=>'checkbox',:value=>'item3'} item 3
Try
%input{:type => "checkbox", :value => "1", :name => "checkbox[]", :id => "id1"} Chk1
%input{:type => "checkbox", :value => "2", :name => "checkbox[]", :id => "id2"} Chk2
%input{:type => "checkbox", :value => "3", :name => "checkbox[]", :id => "id3"} Chk3
Then in the rails or sinatra
puts params[:checkbox]
Then you can see the checked items.

Resources