I am loading data from database and saving it to a constant like this:
profession = Hash.new
Profession.all.each do |p|
profession[p.name] = p.unique_profession
profession[p.name]['descr'] = p.description # problem
profession[p.name]['created_at'] = p.created_at # problem
end
On the line profession[p.name]['descr'] occurs this error:
undefined method `[]=' for 1:Fixnum (NoMethodError)
I want to use profession like:
<div>Profession name: <%= profession[p.name] %></div>
<div>Description: <%= profession[p.name]['descr'] %></div>
How can I make profession work with as one [name] and as two [name][descr] parameters?
You start with an empty hash:
{}
Then, for every Profession record, you assign profession[p.name] = p.unique_profession. Assuming that unique_profession is a Fixnum, that means you get this:
{
'ProfessionName1' => 1,
'ProfessionName2' => 2
}
and so on.
You can't assign sub-keys to Fixnum - it's not a hash. You might instead want one of the following data structures (I'll follow the structure with the code to create it):
{
'ProfessionName1' => {
'descr' => 'Description of Profession 1',
'created_at' => '2016-05-08T22:33:38.093753Z'
},
'ProfessionName2' => {
'descr' => 'Description of Profession 2',
'created_at' => '2015-04-09T21:23:33.093753Z'
}
}
profession = Hash.new
Profession.all.each do |p|
profession[p.name] = Hash.new
profession[p.name]['descr'] = p.description
profession[p.name]['created_at'] = p.created_at
end
or simply an array of hashes containing every property:
[
{
'name' => 'ProfessionName1',
'descr' => 'Description of Profession 1',
'created_at' => '2016-05-08T22:33:38.093753Z'
},
{
'name' => 'ProfessionName2',
'descr' => 'Description of Profession 2',
'created_at' => '2015-04-09T21:23:33.093753Z'
}
]
profession = []
Profession.all.each do |p|
profession << {
'name' => p.name,
'descr' => p.description,
'created_at' => p.created_at
}
end
If you want to use hashes you'll need to store 'unique_profession' into a new key.
profession = Hash.new
Profession.all.each do |p|
profession[p.name] = []
profession[p.name]['unique'] = p.unique_profession
profession[p.name]['descr'] = p.description # problem
profession[p.name]['created_at'] = p.created_at # problem
end
Then you can do this (but note the output will be a number, the contents of unique_profession):
<div>Profession name: <%= profession[p.name]['unique'] %></div>
<div>Description: <%= profession[p.name]['descr'] %></div>
Finally, I'd recommend using symbols instead of strings (as that's the Ruby way):
profession = Hash.new
Profession.all.each do |p|
profession[p.name] = []
profession[p.name][:unique] = p.unique_profession
profession[p.name][:descr] = p.description
profession[p.name][:created_at] = p.created_at
end
Then
<div>Profession name: <%= profession[p.name][:unique] %></div>
<div>Description: <%= profession[p.name][:descr] %></div>
On the other hand you could just store and use the Profession instances directly.
Related
Hash
data = {
:recordset => {
:row => {
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"}
]
}
},
:#xmlns => "http://localhost/test"
}
Code Used
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
I cannot get the following output:
[{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"}
Error message:
TypeError no implicit conversion of Symbol into Integer
It works correctly in case of multi records
data = {
:recordset => {
:row => [{
:property => [
{:name => "Code", :value => "C0001"},
{:name => "Customer", :value => "ROSSI MARIO"},
{:name => "Phone1", :value => "1234567890"}
]
}, {
:property => [
{:name => "Code", :value => "C0002"},
{:name => "Customer", :value => "VERDE VINCENT"},
{:name => "Phone1", :value => "9876543210"},
{:name => "Phone2", :value => "2468101214"}
]
}]
},
:#xmlns => "http://localhost/test"
}
Code used
data.keys
#=> [:recordset, :#xmlns]
data[:recordset][:row].count
#=> 2 # There are 2 set of attribute-value pairs
result = data[:recordset][:row].each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
#=> [
# {"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT", "Phone1"=>"9876543210", "Phone2"=>"2468101214"}
# ]
In the first case data[:recordset][:row] is not an Array, it's a Hash, so when you iterate it, the hash variable becomes the array:
[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]
In the second case, it's an Array, not a Hash, so when you iterate it, it becomes the hash:
{:property=>[{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}, {:name=>"Phone1", :value=>"1234567890"}]}
You're always assuming it's the second format. You could force it into an array, and then flatten by 1 level to treat both instances the same:
result = [data[:recordset][:row]].flatten(1).each_with_object([]) do |hash, out|
out << hash[:property].each_with_object({}) do |h, o|
o[h[:name]] = h[:value]
end
end
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO"}] # result from example 1
# => [{"Code"=>"C0001", "Customer"=>"ROSSI MARIO", "Phone1"=>"1234567890"},
# {"Code"=>"C0002", "Customer"=>"VERDE VINCENT",
# "Phone1"=>"9876543210", "Phone2"=>"2468101214"}] # result from example 2
It's tempting to try and use Kernal#Array() instead of [].flatten(1), but you have to remember that Hash implements to_a to return a nested array of keys and values, so Kernal#Array() doesn't work like you'd want it to:
Array(data[:recordset][:row]) # using the first example data
# => [[:property, [{:name=>"Code", :value=>"C0001"}, {:name=>"Customer", :value=>"ROSSI MARIO"}]]]
You can create an array if it's not an array to normalize the input before processing it.
info = data[:recordset][:row]
info = [info] unless info.is_an? Array
result = info.each_with_object([]) do ....
How I can create a hash like this in a cycle ?
User.items.each do |m|
......
Result:
test = [{:name => 'Unit 1', :price => "10.00"},
{:name => 'Unit 2', :price => "12.00"},
{:name => 'Unit 3', :price => "14.00"}]]
You can use map to return hashes that you build.
Assuming your Item resource responds to name and price, it would look like
test = User.items.map do |m|
{
name: m.name,
price: m.price
}
end
You also can do like this:
Item.connection.select_all("select name, price from items where user_id = xxxxx;")
you will get an array containing hash, like this:
[{"name"=>"xxx", "price"=> xxx},{}......]
<%
old_city = ""
#objects.order("city").each do |obj|
if old_city != obj.city && old_city != ""
old_city = obj.city
%>
--Different city--
<%
end
%>
City: <%= obj.city %>
<%
end
%>
So that output expected is:
Chicago
Chicago
--Different city--
New York
New York
New York
--Different city--
Paris
--Different city--
Rio de Janeiro
Maybe there's some cleaver/different way to do that in rails?
I don't think this is the best code for it...
Thanks!
There are several options, but Enumerable offers a group_by method.
group_by takes a block to define groupings. After grouping it's a matter of iterating over the resulting map's keys.
objs = [
{ :foo => 'baz', :wat => 'kthxbai' },
{ :foo => 'bar', :wat => 'narnar' },
{ :foo => 'plugh', :wat => 'xyzzy' },
{ :foo => 'bar', :wat => 'ohai' },
{ :foo => 'baz', :wat => 'fuuuu' },
{ :foo => 'plugh', :wat => 'jimmies' }
]
grouped = objs.group_by { |o| o[:foo] }
grouped.each do |k, v|
puts "GROUP: #{k}"
v.each { |v| puts v }
end
If you want to order by keys, you can do that too by sorting the keys and retrieving the resulting map's values while iterating over the sorted keys.
If they're ActiveRecords you might want to do the work in SQL/ActiveRecord proper.
Try something like this in the console:
Event.order(:city).group_by(&:city)
This will return a hash where the keys will be the individual cities and the values will be arrays of the corresponding event objects. You can then easily iterate over the hash's keys, and in an inner loop, iterate over the corresponding event objects.
I'm developing an application that displays tabular data in many different areas and I find myself constantly using the same HTML table structure over and over. For example a particular table looks like this:
%table.zebra-striped#user-table{ :cellspacing => "0" }
%colgroup
%col{:id => "email"}
%col{:id => "username"}
%col{:id => "sign-in-count"}
%col{:id => "last-sign-in-at"}
%thead
%tr
%th{:id => "email-head", :scope => "col"} E-mail
%th{:id => "username-head", :scope => "col"} Username
%th{:id => "sign-in-count-head", :scope => "col"} Sign Ins
%th{:id => "last-sign-in-at-head", :scope => "col"} Last Sign In
%tbody
- #users.each do |user|
%tr{ :class => zebra }
%td
=h user.email
%td
=h user.username
%td
=h user.sign_in_count
%td
=h user.last_sign_in_at
Ideally, I would like to create some kind of helper method where I could do something like:
= custom_table_for #users do
= column :email
= column :username do |user|
= link_to user.username, user_path(user)
= column "Sign Ins", :sign_in_count
= column :last_sign_in_at
This way I can change the formatting of the data in the columns and the column header names if I'm not happy with default values, but have the table generated for me.
I suppose I could create a normal helper, but I'd have to use arrays and I have no idea how I could include custom data formatting per column.
active_admin has something similar to this which you can see here: http://activeadmin.info/docs/3-index-pages/index-as-table.html
Any leads or ideas would be greatly appreciated.
I just came up with this:
A few points:
The line #columns = [] is a reset so you can call it more than once.
The yield in the custom_table_for calls the block that you pass it.
The block in the column method is stored and called in custom_table_for if it is set.
I included a sample class to show the usage too.
please note I did this outside of a rails app and you almost certainly want to use http://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag instead of the p "<table>" this is merely for sample purposes when you run it in the console.
module TableHelper
def custom_table_for(items)
#columns = []
yield
p "<table>"
#columns.each do |c|
p "<th>#{c[:value]}</th>"
end
items.each do |e|
p "<tr>"
#columns.each do |c|
e[c[:name]] = c[:block].call(e[c[:name]]) if c[:block]
p "<td>#{e[c[:name]]}</td>"
end
p "</tr>"
end
p "</table>"
end
def column(name, value = nil, &block)
value = name unless value
#columns << {:name => name, :value => value, :block => block}
end
end
class ExampleTable
include TableHelper
def test
#users = [{:email => "Email 1", :username => "Test User"}, {:email => "Email 2", :username => "Test User 2"}]
custom_table_for #users do
column :email, "Email"
column :username do |user|
user.upcase
end
end
end
end
et = ExampleTable.new
et.test
UPDATE
I migrated this to rails to use content_tags
module TableHelper
def custom_table_for(items)
#columns = []
yield
content_tag :table do
thead + tbody(items)
end
end
def thead
content_tag :thead do
content_tag :tr do
#columns.each do |c|
concat(content_tag(:th, c[:value]))
end
end
end
end
def tbody(items)
content_tag :tbody do
items.each { |e|
concat(content_tag(:tr){
#columns.each { |c|
e[c[:name]] = c[:block].call(e[c[:name]]) if c[:block]
concat(content_tag(:td, e[c[:name]]))
}
})
}
end
end
def column(name, value = nil, &block)
value = name unless value
#columns << {:name => name, :value => value, :block => block}
end
end
To compliment #gazler's response, here's a way to make a table of a single resource-- column one for attribute names, column two for their values:
module TableHelper
#resource = nil
def simple_table_for(resource)
#resource = resource
content_tag :table do
content_tag :tbody do
yield
end
end
end
def row(key, label = nil, &block)
if key.is_a? String
label = key
end
content_tag(:tr) {
concat content_tag :td, label || key.capitalize
concat content_tag(:td ){
if block_given?
yield
else
#resource.send(key)
end
}
}
end
end
Absolute RoR newbie here, I'm trying to render out multiple leagues in a loop, incrementing the div_# each time, here's a cut down version, without the html. It works when I hard code div_1 or div_2 to be sorted, but div_name doesn't work, even though it has the right contents I need it to be seen as the array.
<% div_1 = Array.new
div_1 << { :Name => 'Rob', :Played => '2', :Won => '1', :Lost => 1, :Points => 4}
div_2 = Array.new
div_2 << { :Name => 'Gavin', :Played => '2', :Won => '1', :Lost => 1, :Points => 4}
for i in (1..2)
i = i.to_s
div_name = "div_" + i
div_name.sort_by { |position| position[:Points] }.reverse!.each do |position| %>
<%= position[:Name] %>
There are lots of problems here:
div_1 = Array.new
div_1 << { :Name => 'Rob', :Played => '2', :Won => '1', :Lost => 1, :Points => 4}
div_1 is now an array with a single element, which is a hash. You don't need an array if you'll just have one element in it.
for i in (1..2)
Where's the block for this loop? After that statement, i is still undefined, so when you call
i = i.to_s
you'll get a NameError.
div_name = "div_" + i
Even if i == 1, div_name will be a string with value 'div_1', and not a copy or instance of the div_1 variable you define above.
div_name.sort_by { |position| position[:Points] }.reverse!.each do |position| %>
Now you're trying to call sort_by on a string, which doesn't respond to that, because it doesn't make sense.
<%= position[:Name] %>
You don't have a variable named position defined at this scope.
Also, when you find yourself putting lots of logic inside of a view within <% %> tags, that's a sign that you need to move that code elsewhere, like to the controller. You could define and calculate #positions as an array of hashes in the controller, and then in the view do something like:
<% #positions.each do |position| %>
<%= position[:name] %>
<% end %>