I'm new to Stack overflow so kindly excuse if my question deviates from the expected standard.
I processed a log file to a below format and I would like to form a HTML representation from the below data structure.
holder = [
{:states=>"Texas"}
{:cities=>"Antonio", :data=>{"Ron"=>"26", "Rock"=>"23", "Jake"=>"33"}}
{:cities=>"West_CIT", :data=>{}}
{:cities=>"Austin", :data=>{"Ron"=>"26", "Mike"=>"53", "Jake"=>"36"}}
{:states=>"California"}
{:cities=>"Sacramento", :data=>{"Jill"=>"584", "Rudy"=>"3"}}
{:cities=>"Los Angeles", :data=>{"Jill"=>"4", "Rudy"=>"2"}}
{:states=>"Georgia"}
{:cities=>"Atlanta", :data=>{"Ron"=>"6", "Ross"=>"2", "Jake"=>"35"}}
{:cities=>"Athens", :data=>{"Jill"=>"16", "Mike"=>"4", "Reeves"=>"8"}} ]
I'm trying to create a HTML file which displays the States only on the Top page, like below
<h1> States and Associate Demat details <h1>
Texas
California
Georgia
but on clicking any State it should expand to a below format holding the sub details
Texas
Antonio Ron 26
Rock 23
Jake 33
West_CIT
Austin Ron 26
Mike 53
Jake 36
California
Georgia
Likewise the other states.
I've gone thru the Ruby documentation as well as the Nokogiri gem but with my limited knowledge in ruby (or any programming language) hampers my progress.. Is it really possible with Ruby or should I go with PHP (learn again). Looking for the guidance here, thank you.
P.S I've taken this as a self assignment in an attempt to improve my Ruby (or programming skills)
What I wrote :
holder = []
counter = -1
text = File.open("states.log").read
text.each_line do |line|
line.strip!
next if line.empty?
next if line.include?('persons') || line.include?('demat')
next if ['-','*'].include? line[0]
chip = line.split ' '
if chip.size == 1 and line.start_with?('state')
holder[counter += 1] = {states: line, data: {}}
next
elsif chip.size == 1 and chip = /^(?!.*state_).*$/
holder[counter += 1] = {cities: line, data: {}}
next
end
chip.each_cons(2) do |key, value|
holder[counter][:data][key] = value
end
end
puts holder
Adding the sample log file (raw)
state_Texas
Antonio
persons demat
------------------------------ ----------
Ron 26
Ron 23
Jake 33
=========================================
----Persons
West_CIT
persons demat
------------------------------ ----------
=========================================
----Persons
Austin
persons demat
------------------------------ ----------
Ron 26
Mike 53
Jake 36
=========================================
state_California
Sacramento
persons demat
------------------------------ ----------
Jill 584
Rudy 3
=========================================
---- Persons
Los Angeles
persons demat
------------------------------ ----------
Jill 4
Rudy 2
=========================================
Likewise .......
First thing first: if you need to exchange data between applications I suggest to stick with standard formats like JSON or YAML. I don't know if you can control the data logging, but if you can, I suggest to change the code there.
That log file is really a mess but it contains enough information for convert it into a ruby data structure like arrays and hashes.
There is always a better way but I ended up with this solution.
REJECT_THIS = ["", "------------------------------ ----------", "----Persons", "---- Persons", "persons demat"]
holder = []
separator = '|||'
# here we store the file into the holder, skipping rows in REJECT_THIS
text = File.open("_states.log").read
text.each_line do |line|
line = line.split.join(' ')
holder << line unless REJECT_THIS.include? line
end
# just to change the separator mark into a shorter one
holder.map! { |e| e == "=========================================" ? separator : e}
# map where to split the array grouping by state
split_idxs = holder.map.with_index { |e, i| e [0..4] == 'state' ? i : 0}.uniq[1..-1]<<holder.size
# split the array in states using the index map using the my_split_at_index method and building the states hash
holder = holder.my_split_at_index(split_idxs).map { |element| {element.shift => element} }
# remove 'state_' and convert the key to symbol
holder.map! { |e| e.transform_keys { |key| key[6..-1].to_sym } }
# splits subarrays by separator then build the nested hash
holder.map! do |array|
array.transform_values do |sub_array|
split_idxs = sub_array.map.with_index { |e, i| e == separator ? i : 0 }.uniq[1..-1]
sub_array = sub_array.my_split_at_index(split_idxs).map! { |e| e[0] == separator ? e[1..-1] : e }
sub_array.map { |element| {city: element.shift, people: element} }
end
end
# splits the people string
holder.map do |state|
state.transform_values do |array|
array.map do |hash|
hash[:people].map! { |string| string.split(' ') }
end
end
end
p holder
In the code I used this Array monkey patch
class Array
def my_split_at_index(indexes = [])
shift = 0
splitted = []
indexes.map do |index|
splitted << self[shift..index-1]
shift = index
end
splitted
end
end
The variable holder now is an array of nested hashes that you can use with ERB in a code like Dan Hilton posted. The data structure of holder is not the same, so you need to tweak the code.
One last thing, to see how the structure of your data as a YAML looks:
require 'yaml'
puts holder.to_yaml
As iGian said you can use ERB templates to create your HTML. I would also recommend changing your data structure to be easier to iterate. You are currently using the array order to determine where the cities are. This requires additional checks on the template side. Instead, the cities should be nested in the states hash to look like: {:state => "Texas", :cities=>[{:name => "Antonio", :data=>{"Ron"=>"26", "Rock"=>"23", "Jake"=>"33"}]}. This way you have an array of cities for a given state. Using this data structure, your template would look something like:
template = ERB.new <<-EOF
<h1> States and Associate Demat details <h1>
<% Holder.each do |state| %>
<h2><%= state[:state] %></h2>
<% state[:cities].each do |city| %>
<h3><%= city[:name] %></h3>
<% city[:data].each do |name, value| %>
<%= name %> - <%= value %>
<% end %>
<% end %>
<% end %>
EOF
Note that using <%= %> with the = is used to wrap ruby expressions that you want to show up on your template. On the other hand <% %> is used to set variables and iterate through your data structure, but will not show up in your template.
In your case, the use of templating is probably overkill and you can get away with using File.write('index.html') but getting ERB practice is useful because it will be directly applicable if you decide to learn Ruby on Rails.
Related
I'm trying to take many posts with example text "you can find other #apple #orchard examples at www.google.com and www.bing.com #funfruit" and display the text to the user with URLs and #tags linking to their appropriate routes.
I have successfully done this with text that only contains any number of #tags, or a single URL, with the following code:
application_controller.rb
def splice_posts(posts, ptags, spliced)
# Build all posts as items in spliced, with each item an post_pieces array
posts.reverse.each do |post|
tag_indices = []
tag_links = []
# Get post URLs: [{:url=>"www.google.com", :indices=>[209, 223]}]
post_links = extract_urls_with_indices(post.text)
# Save each as rails style link with indices
# For each of the ptags associated with post
ptags.where(post_id:post.id).each do |ptag|
# Store hashtag's start/stop indices for splicing post
tag_indices.append([ptag.index_start, ptag.index_end])
# Store hashtag links for splicing post
tag_links.append(view_context.link_to '#' + ptag.hashtag, atag_path(Atag.find_by(id:ptag.atag_id).id),
:class => 'post_hashtag', :remote => true, :onclick => "location.href='#top'")
end
# Create and store post as post_pieces in spliced
# If there are no hashtags
if tag_indices.length == 0
# And no links
if post_links.length == 0
spliced.append([post.text, post.id])
# But links
else
spliced.append([post.text[0..post_links[0][:indices][0]-2],
view_context.link_to(post_links[0][:url], post_links[0][:url], target: "_blank"),
post.text[post_links[0][:indices][1]..-1], post.id])
end
# Elsif there is one hashtag
elsif tag_indices.length == 1
if post.text[0] == '#'
spliced.append([post.text[2..tag_indices[0][0]], tag_links[0],
post.text[tag_indices[0][1]..-1], post.id])
else
spliced.append([post.text[0..tag_indices[0][0]-2], tag_links[0],
post.text[tag_indices[0][1]..-1], post.id])
end
# Else there are multiple hashtags, splice them in and store
else
# Reset counter for number of tags in this post
#tag_count = 0
# If post begins with tag, no text before first tag
if tag_indices[0][0] == 0
post_pieces = []
# Else store text before first tag
else
post_pieces = [post.text[0..tag_indices[0][0]-2]]
end
# Build core of post_pieces, splicing together tags and text
tag_indices.each do |indice|
post_pieces.append(tag_links[#tag_count])
post_pieces.append(post.text[indice[1]..tag_indices[#tag_count+1][0]-2])
if #tag_count < (tag_indices.length-2)
#tag_count += 1
else
# Do nothing
end
end
# Knock off the junk at the end
post_pieces.pop
post_pieces.pop
# Finish compiling post_pieces and store it in spliced
post_pieces.append(tag_links[-1])
post_pieces.append(post.text[tag_indices[-1][1]..-1])
# Make last item in array post id for comment association purposes
post_pieces.append(post.id)
spliced.append(post_pieces)
end
end
end
The spliced posts are then easily served in the view piece by piece:
<% #posts_spliced.each do |post_pieces| %>
<%# Build post from pieces (text and hashtags), excluding last element which is post_id %>
<% post_pieces[0..-2].each do |piece| %>
<%= piece %>
<% end %>
<% end %>
The problem is that this implementation is convoluted to begin with, and trying to patch it with dozens of nested if/else statement to handle URLs seems like madness, as I'm suspecting that a more experienced software engineer/rails developer could enlighten me on how to do this with a fraction of the code.
To clarify I have the following variables already available for each post (with examples) :
post = 'some text with #tags and www.urls.com potentially #multiple of each.com'
post_urls = [{:url=>"www.urls.com", :indices=>[25, 37]}, {:url=>"each.com", :indices=>[63, 71]}]
post_tags = [{:hashtag=>"tags", :indices=>[15, 20]}, {:hashtag=>"multiple", :indices=>[50, 59]}]
I'm thinking that a more practical implementation might involve the indices more directly, but perhaps breaking the post into elements in an array is the wrong idea altogether, or perhaps there is an easier way, but before I spend a couple hours conceptualizing the logic and writing the code for another possible unideal solution, I thought I should see if someone could enlighten me here.
Thanks so much!
Unless I'm missing something important, I think you've overcomplicated things.
First, you split the string by spaces.
string = "whatever string typed in by user"
split_string = string.split
Then you map the split-string-array according to your requirements and join the results.
# create show_hashtag(str) and show_link(str) helpers
split_string.map do |str|
if str.starts_with?('#')
show_hashtag(str)
elsif url_regexp.match(str) # you must define url_regexp
show_link(str)
else
str
end
end.join(' ')
You won't have to worry about positions of the text, tags, or links because map will take care of it for you.
Wrap all of that in a helper and in your view you could do the following:
<%= your_helper(string_typed_in_by_user).html_safe %>
Watch out for the user typing in HTML though!
I have a controller that calls a find_photos method, passing it a query string (name of file)
class BrandingPhoto < ActiveRecord::Base
def self.find_photos(query)
require "find"
found_photos = []
Find.find("u_photos/photo_browse/photos/") do |img_path|
# break off just the filename from full path
img = img_path.split('/').last
if query.blank? || query.empty?
# if query is blank, they submitted the form with browse all- return all photos
found_photos << img
else
# otherwise see if the file includes their query and return it
found_photos << img if img.include?(query)
end
end
found_photos.empty? ? "no results found" : found_photos
end
end
This is just searching a directory full of photos- there is no table backing this.
Ideally what I would like is to be able to limit the number of results returned by find_photos to around 10-15, then fetch the next 10-15 results as needed.
I was thinking that the code to do this might involve looping through 10 times and grabbing those files- store the last filename in a variable or as a parameter, and then send that variable back to the method, telling it to continue the search from that filename.
This assumes that the files are looped through in the same order everytime, and that there is no simpler way to accomplish this.
If there are any suggestions, I'd love to hear them/see some examples of how you'd accomplish this.
Thank you.
The first thing that comes to mind for this problem is to cut the array down after you come out of the loop. This wouldn't work well with a ton of files though A different solution might be to add a break for the size of the array viz. break if found_photos.length > 10 inside the loop
It's not too hard to do what you want, but you need to consider how you'll handle entries that are added or removed in-between page loads, filenames with UTF-8 or Unicode characters, and embedded/parent directories.
This is old-school code for the basis for what you're talking about:
require 'erb'
require 'sinatra'
get '/list_photos' do
dir = params[ :dir ]
offset = params[ :offset ].to_i
num = params[ :num ].to_i
files = Dir.entries(dir).reject{ |fn| fn[/^\./] || File.directory?(File.join(dir, fn)) }
total_files = files.size
prev_a = next_a = ''
if (offset > 0)
prev_a = "<a href='/list_photos?dir=#{ dir }&num=#{ num }&offset=#{ [ 0, offset - num ].max }'><< Previous</a>"
end
if (offset < total_files)
next_a = "<a href='/list_photos?dir=#{ dir }&num=#{ num }&offset=#{ [ total_files, offset + num ].min }'>Next >></a>"
end
files_to_display = files[offset, num]
template = ERB.new <<EOF
<html>
<head></head>
<body>
<table>
<% files_to_display.each do |f| %>
<tr><td><%= f %></td></tr>
<% end %>
</table>
<%= prev_a %> | <%= total_files %> files | <%= next_a %>
</body>
</html>
EOF
content_type 'text/html'
template.result(binding)
end
It's a little Sinatra server, so save it as test.rb and run from the command-line using:
ruby test.rb
In a browser connect to the running Sinatra server using a URL like:
http://hostname:4567/list_photos?dir=/path/to/image/files&num=10&offset=0
I'm using Sinatra for convenience, but the guts of the routine is the basis for what you want. How to convert it into Rails terms is left as an exercise for the reader.
I am building a prototype of a education application using Rails 3, omniauth and the facebook graph api. So when a User log in to my application he uses his facebook account, I grab all his education history and his friends education_history.
I would like to group every User friends education likes this:
I have tried something like this:
<ul class="friends-list">
<%= current_user.friends.group_by(&:highschool_name) do |highschool_name|
p "<li>#{highschool_name}</li>"
end
%>
</ul>
And I get a syntax error.
The User tabel look like this:
[id, name, image, location, highschool_name, highschool_year, college_name, college_year, graduteschool_name, graduate_year ]
And the Friend tabel looks like this:
[id, uid, name, image, higschool_name, college_name, graduateschool_name, user_id]
How do solve my problem using active record, without loops because their are not effectivity.. right?
You can't use p or puts in ERB files. Think of ERB files as one big string concatenated together. Like "string 1" + "string 2" + "string 3".
That's all ERB does - it just pastes strings together into one big string. You can't call puts inside this concatenation operation. So everything in the ERB file needs to be a string. The output from a puts call just 'goes up in smoke' since a puts call does not return a string, it writes to stdout instead.
Next we look at group_by: it returns a Hash:
---------------------------------------------------- Enumerable#group_by
enum.group_by {| obj | block } => a_hash
------------------------------------------------------------------------
Returns a hash, which keys are evaluated result from the block,
and values are arrays of elements in enum corresponding to the key.
(1..6).group_by {|i| i%3} #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
So putting everything together we could do something like this:
<% current_user.friends.group_by(&:highschool_name).each do |hsname, friends| %>
<% next if hsname.blank? %>
<li><%= hsname %></li>
<% friends.each do |friend| %>
<%= image_tag(friend.img_url) %> # <- Or wherever you get the img url from
<% end %>
<% end %>
#search = Sunspot.search(Event, Person, Organization) do
keywords params[:q]
order_by(:score)
end
Based on the search results I'd like to create a list of Models with counts for each model.
Events (12)
People (5)
Organizations (3)
Is there a way to do this type of grouping in Sunspot?
<% #search.each_hit_with_result do |hit, result| -%>
<%= result.class %> <!- Gives me Model, but repeated -->
<% end %>
There is probably a smarter way, but one possible way is to get array of the classes like this
classes = #search.results.map(&:class) # this will give array of returned classes
then do as suggested in this link
h = Hash.new(0)
classes.each { | v | h.store(v, h[v]+1) }
# h = { 3=>3, 2=>1, 1=>1, 4=>1 }
hope that helps
I'm working on a RoR application that deals with users creating entries. These entries have a price, a boolean which signifies the entry as either an income or an expense, and via the acts_as_taggable_on plugin, tags. Users act_as_taggers, so I'm able to apply ownership to tags.
I'm trying to find the users tag that has the highest sum (income entries - expense entries) as well as the tag with the lowest sum.
Right now I'm able to find all tags that the user has created:
#all_tags = current_user.owned_tags
I'm then able to go through each of the users entries, tag-by-tag, and create an array with the sums of prices for each tag:
#tag_groups = Array.new
for tag in #all_tags
entries_tagged_with_tag = current_user.entries.tagged_with(tag)
#tag_groups << entries_tagged_with_tag.sum('price')
end
This gives me the summed prices for each tag, but I'm then missing the tag name that is associated with that sum.
However, there's a pretty big catch here: An entries price by itself doesn't signify if the entry is an income or an expense. Each entry has an 'is_income' field; true meaning the entry is an income, and false means that the entry is an expense. So, the total for a tag is actually, is psuedo code:
all_incomes_with_tag(tag) - all_expenses_with_tag(tag)
The information that I'm looking to show in my view is: Tag Name, Number of Associated Entries, and Total Price. I would like to show this information for the largest expense tag, and the largest income tag.
I hope I've provided enough information. Any help, even just a point toward the right direction, would be greatly appreciated!
UPDATE
I've made some progress. Here's my current Controller:
#all_tags = current_user.owned_tags
#tag_groups = Array.new
for tag in #all_tags
income_entries_tagged_with_tag = current_user.entries.tagged_with(tag, :conditions => ['is_income = ?', true])
expense_entries_tagged_with_tag = current_user.entries.tagged_with(tag, :conditions => ['is_income = ?', false])
all_entries_tagged_with_tag = current_user.entries.tagged_with(tag)
tag_total_income = income_entries_tagged_with_tag.sum('price')
tag_total_expense = expense_entries_tagged_with_tag.sum('price')
tag_total = tag_total_income - tag_total_expense
#tag_groups << { :total_price => tag_total, :tag_name => tag.name, :number_of_entries => all_entries_tagged_with_tag.count }
end
and here's my View:
<% #tag_groups.sort_by { |tag| tag[:total_price] }.each do |tag| %>
<p><%= tag[:tag_name] %> — <%= number_to_currency(tag[:total_price]) %> (<%= tag[:number_of_entries] %>)</p>
<% end %>
Which gives me the following output:
food — $-32.50 (1)
lunch — $-32.50 (1)
coffee — $-32.50 (1)
business — $-32.50 (1)
development — $930.00 (1)
groceries — $930.00 (1)
babies — $930.00 (1)
second tag — $933.45 (1)
first tag — $933.45 (1)
personal — $1,022.00 (2)
I feel like I'm nearly there...
I am not familiar with the plugins in question. But if #tag_groups is the collection you are using to try to access the attributes you need, then all your currently doing is sending in the #sum('price') value.
You will want to make sure your collection gets all the data you want to access in the view.
#tag_groups << {"tag_name" => ..., "no_of_ass" => ..., "total" => ...}
Hash {} is always nice since it's a key:value pairs, but array [] is fine if you want to index it.