Ruby Rails Excel Format Array Manipulation - ruby-on-rails

I need some help/idea on how to dynamically position values in a 2D array.
Basically, I have a condition that must be met and that determines which column should the value be written.
This is what I have (a loop that pass conditions before writing the value):
#records.each do |client|
client_info = Report.get_client_info(client)
client_address = Report.get_client_address(client)
client_records = []
client_records << [client_info.id, client_info.full_name]
if #include_address == "1"
client_address.each_with_index do |address, i|
if client_records[i].present?
client_records[i][2] = address.full_address
client_records[i][3] = address.address_type
else
client_records << ["","", address.full_address, address.address_type]
end
end
end
if #include_contact == "1"
client_contact.each_with_index do |contact, i|
if client_records[i].present?
client_records[i][4] = contact.value
client_records[i][5] = contact.label
else
client_records << ["","","","", contact.value, contact.label]
end
end
end
end
Problem is: IF I want to make another condition, and the condition in between the two (conditions) is not true, the position of the column value set by: client_records[i][2] >> 2,3 etc... is hard coded, instead of the third condition occupying the place of the second condition's columns, it naturally leaves it blank and stays at the same place.
This is how it would look like:
Instead of:
How can I work around this?

For example, try that:
#records.each do |client|
client_info = Report.get_client_info(client)
client_address = Report.get_client_address(client)
client_records = []
line = [client_info.id, client_info.full_name]
[client_address.count, client_contact.count].max.times do |i|
if #include_address == "1" && client_address[i]
line << client_address[i].full_address << client_address[i].address_type
else
line << '' << ''
end
if ##include_contact == "1" && client_contact[i]
line << client_contact[i].value << client_contact[i].label
else
line << '' << ''
end
client_records << line.clone
line = ['', '']
end
end

Related

Ruby hash path to each leaf

First of all I beg your pardon if this question already exists, I deeply searched for a solution here but I've been able to find it, nevertheless I feel it's a problem so common that is seems so strange to not find anything here...
My struggle is the following: given an hash, I need to return all the PATHS to each leaf as an array of strings; so, for example:
{:a=> 1} gives ['a']
{:a=>{:b=>3, :c=>4} returns an array with two results: ["a.b", "a.c"]
{:a=>[1, {:b=>2}]} will result in ["a.0", "a.1.b"]
and so on...
I have found only partial solutions to this and with dozens of codelines. like this
def pathify
self.keys.inject([]) do |acc, element|
return acc if element.blank?
if !(element.is_a?(Hash) || element.is_a?(Array))
if acc.last.is_a?(Array)
acc[acc.size-1] = acc.last.join('.')
else
acc << element.to_s
end
end
if element.is_a?(Hash)
element.keys.each do |key|
if acc.last.is_a?(Array)
acc.last << key.to_s
else
acc << [key.to_s]
end
element[key].pathify
end
end
if element.is_a?(Array)
acc << element.map(&:pathify)
end
acc
end
end
But it does not work in all cases and is extremely inefficient. Summarizing: is there any way to "pathify" an hash to return all the paths to each leaf in form of array of strings?
Thank you for the help!
Edited
Adding some specs
for {} it returns []
for {:a=>1} it returns ["a"]
for {:a=>1, :b=>1} it returns ["a", "b"]
for {:a=>{:b=>1}} it returns ["a.b"] (FAILED - 1) got: ["a"]
for {:a=>{:b=>1, :c=>2}} it returns ["a.b", "a.c"] (FAILED - 2) got: ["a"]
for {:a=>[1]} it returns ["a.0"] (FAILED - 3) got: ["a"]
for {:a=>[1, "b"]} it returns ["a.0", "a.1"] (FAILED - 4) got: ["a"]
def show(key, path)
if path.is_a? Array
path.map {|p| "#{key}.#{p}"}
else
path == "" ? key.to_s : "#{key}.#{path}"
end
end
def pathify(input)
if input.is_a? Hash
input.map do |k,v|
sub_path = pathify(v)
show(k, sub_path)
end.flatten
elsif input.is_a? Array
input.map.with_index do |v, i|
sub_path = pathify(v)
show(i, sub_path)
end.flatten
else
""
end
end
def leaf_paths(enum)
return unless [Hash, Array].include? enum.class
[].tap do |result|
if enum.is_a?(Hash)
enum.each { |k, v| result = attach_leaf_paths(k, v, result) }
elsif enum.is_a?(Array)
enum.each_with_index { |elem, index| result = attach_leaf_paths(index, elem, result) }
end
end
end
def attach_leaf_paths(key, value, result)
if (children = leaf_paths(value))
children.each { |child| result << "#{key}.#{child}" }
else
result << key.to_s
end
result
end
This is very similar to https://github.com/wteuber/yaml_normalizer/blob/b85dca7357df00757c471acb5dadb79a53dd27c1/lib/yaml_normalizer/ext/namespaced.rb
So I tweaked the code a bit to fit your needs:
module Leafs
def leafs(namespace = [], tree = {})
each do |key, value|
child_ns = namespace.dup << key
if value.instance_of?(Hash)
value.extend(Leafs).leafs child_ns, tree
elsif value.instance_of?(Array)
value.each.with_index.inject({}) {|h, (v,k)| h[k]=v; h}.extend(Leafs).leafs child_ns, tree
else
tree[child_ns.join('.')] = value
end
end
tree.keys.to_a
end
end
Here is how to use it:
h = {a: [1, "b"], c: {d:1}}
h.extend(Leafs)
h.leafs
# => ["a.0", "a.1", "c.d"]
I hope you find this helpful.
def pathify(what)
paths = []
if what.is_a?(Array)
what.each_with_index do | element, index |
paths+= pathify(element).map{|e| index.to_s + '.' + e.to_s}
end
elsif what.is_a?(Hash)
what.each do |k,v|
paths+= pathify(v).map{|e| k.to_s + '.' + e.to_s}
end
else
paths.append('')
end
paths.map{|e| e.delete_suffix('.')}
end

How do I display database calls from Controller into view, to be viewed in HTML

I was handed a project from someone else, it's in Ruby On Rails, which I know VERY LITTLE. Basically, there is an EXPORT button, that the user clicks to send data to a CSV. I am tasked with sending this data to the view to be seen in HTML. (Thinking I could use dataTables). I have tried following examples, such as:
#example = StudentGroup.where(survey_id: #survey.id).order("groupNum")
and then using <%= #example %> in the view just to see the data and I get nothing. (Also extremely new to MySQL). I'll post the method, if ANYONE can help me, I'd very much appreciate it.
def download_results
if (user_signed_in?)
else
redirect_to new_user_session_path
end
#survey = Survey.find(params[:survey_to_view])
filename = #survey.name + " - " + Date.today.to_formatted_s(:short)
require "csv"
CSV.open(#survey.name+".csv", "wb") do |csv|
csv << [filename]
StudentGroup.where(survey_id: #survey.id).order("groupNum")
csv << []
csv << ["Summarized Results"]
csv << ["UCA","Group Number","Criteria 1","Criteria 2","Criteria 3","Criteria 4","Criteria 5","Criteria 6","Criteria 7","Criteria 8","Overall Team Contribution","Average(Would Work With Again)","Average(C1..C8)","Overall Team Contribution MINUS Average(C1..C9)"]
questions = #survey.questions
numQuestions = 0
questions.each do |q|
if(q.question_type != 2 && q.question_type != 4)
numQuestions = numQuestions+1
end
end
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
size = othersInGroup.count-1
arr = []
criteria = SurveyQuestionDatum.where("number > 24 AND number < 35")
multiAvg = 0
teamCont = 0
criteria.each do |c|
avg = 0
othersInGroup.each do |o|
a = Answer.where(survey_question_datum_id: c.id, student_group_id: o.id).first
if(o.uca != g.uca)
if(a.nil?)
size = size-1
else
avg = avg + a.answer[g.uca].to_i
end
end
end
avg = avg.to_f/size
if(c.number == 33)
teamCont = avg
end
if(c.number < 33)
multiAvg = multiAvg+avg
end
arr << avg
end
multiAvg = multiAvg.to_f/8
arr << multiAvg
arr << teamCont-multiAvg
arr.insert(0,g.uca, g.groupNum)
csv << arr
end
end
csv << []
csv << []
csv << ["Raw Student Answers"]
groups = StudentGroup.where(survey_id: #survey.id).order("groupNum")
size = groups.count
csv << ["UCA", "F-Number", "Group Number"]
groups.each do |g|
answersCount = Answer.where(student_group_id: g.id).count
if(answersCount == numQuestions && answersCount != 0)
othersInGroup = StudentGroup.where(groupNum: g.groupNum, survey_id: #survey.id).order("groupNum")
csv << []
csv << [g.uca, g.FNum, g.groupNum]
answers = Answer.where(student_group_id: g.id)
csv << ["Question Number", "Question", "Answer"]
answers.each do |a|
datum = a.survey_question_datum
question = datum.question
#question_types = {"0" => "short", "1" => "paragraph",
#2" => "title", "3" => "fivept", "4" => "fixed",
#5" =>"ranking", "6"=>"tenpoints","7"=>"hundredpoints"}
ansText = ""
if(question.question_type == 0)
ansText = a.answer
elsif (question.question_type == 1)
if(question.rule == 'perMember')
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
elsif(question.rule == 'default')
ansText = a.answer
end
else (question.question_type == 3)
othersInGroup.each do |o|
ansText = ansText+"#{o.uca},#{a.answer[o.uca]},"
end
end
ansText = ansText.chomp(',')
ansText = ansText.split(',')
ansText.insert(0,datum.number,question.question_text)
csv << ansText
end
end
end
end
send_file(#survey.name+".csv", :filename => filename+".csv")
end
You need a new controller action. Take a look at http://guides.rubyonrails.org/layouts_and_rendering.html
Create an index (or show, or whatever you want to call it, maybe example) action. Make sure it is in your routes.
http://guides.rubyonrails.org/getting_started.html#adding-a-route-for-comments
do not use the download_results code.
set your #example variable the way you were trying to do.
create a view for your index action
add the data to your index view.
If you put code in your download_results method (action) it will never get rendered because of the send_file method call.
Did you create a brand new controller / action / view? Did you use generators? Have you really practiced doing this setup exactly the way the examples, videos, tutorials say to do it? If you have, you have seen how all the pieces (models, controllers, actions, views) come together. You should have seen how render statements come into play. Do that, exactly as the tutorials say to do it and you will get the idea.
If you want to use the same content that the download action uses, refactor the code to extract a method that is used both actions.
This is related to respond_to part, check the docs.
send_file(#survey.name+".csv", :filename => filename+".csv")
Your code above simply means you click the button, the controller will respond you with a csv file. So, if you want a html, the controller should be able to respond to html as well.

Finding letters that are near, exact or not in a user input string

I am currently developing a small modified version of Hangman in Rails for children. The game starts by randomly generating a word from a text file and the user has to guess the word by entering a four letter word. Each word is the split by each character for example "r", "e", "a", "l" and returns a message on how they are to the word.
Random Generated word is "real"
Input
rlax
Output
Correct, Close, Correct, Incorrect
I have tried other things which I have found online but haven't worked and I am fairly new to Ruby and Rails. Hopefully someone can guide me in the right direction.
Here is some code
def letterCheck(lookAtLetter)
lookAHead = lookAtLetter =~ /[[:alpha:]]/
end
def displayWord
$ranWordBool.each_index do |i|
if($ranWordBool[i])
print $ranWordArray[i]
$isWin += 1
else
print "_"
end
end
end
def gameLoop
turns = 10
turnsLeft = 0
lettersUsed = []
while(turnsLeft < turns)
$isWin = 0
displayWord
if($isWin == $ranWordBool.length)
system "cls"
puts "1: Quit"
puts "The word is #{$ranWord} and You Win"
puts "Press any key to continue"
return
end
print "\n" + "Words Used: "
lettersUsed.each_index do |looper|
print " #{lettersUsed[looper]} "
end
puts "\n" + "Turns left: #{turns - turnsLeft}"
puts "Enter a word"
input = gets.chomp
system "cls"
if(input.length != 4)
puts "Please enter 4 lettered word"
elsif(letterCheck(input))
if(lettersUsed.include?(input))
puts "#{input} already choosen"
elsif($ranWordArray.include?(input))
puts "Close"
$ranWordArray.each_index do |i|
if(input == $ranWordArray[i])
$ranWordBool[i] = true
end
if($ranWordBool[i] = true)
puts "Correct"
else
puts "Incorrect"
end
end
else
lettersUsed << input
turnsLeft += 1
end
else
puts "Not a letter"
end
end
puts "You lose"
puts "The word was #{$ranWord}"
puts "Press any key to continue"
end
words = []
File.foreach('words.txt') do |line|
words << line.chomp
end
while(true)
$ranWord = words[rand(words.length) + 1]
$ranWordArray = $ranWord.chars
$ranWordBool = []
$ranWordArray.each_index do |i|
$ranWordBool[i] = false
end
system "cls"
gameLoop
input = gets.chomp
shouldQuit(input)
end
Something like that:
# Picking random word to guess
word = ['open', 'real', 'hang', 'mice'].sample
loop do
puts "So, guess the word:"
input_word = gets.strip
if word == input_word
puts("You are right, the word is: #{input_word}")
break
end
puts "You typed: #{input_word}"
# Split both the word to guess and the suggested word into array of letters
word_in_letters = word.split('')
input_in_letters = input_word.split('')
result = []
# Iterate over each letter in the word to guess
word_in_letters.each_with_index do |letter, index|
# Pick the corresponding letter in the entered word
letter_from_input = input_in_letters[index]
if letter == letter_from_input
result << "#{letter_from_input} - Correct"
next
end
# Take nearby letters by nearby indexes
# `reject` is here to skip negative indexes
# ie: letter 'i' in a word "mice"
# this will return 'm' and 'c'
# ie: letter 'm' in a word "mice"
# this will return 'i'
letters_around =
[index - 1, index + 1]
.reject { |i| i < 0 }
.map { |i| word_in_letters[i] }
if letters_around.include?(letter_from_input)
result << "#{letter_from_input} - Close"
next
end
result << "#{letter_from_input} - Incorrect"
end
puts result.join("\n")
end

Test-first-ruby 13_xml_document

I am working on test-first-ruby-master (you can find it at https://github.com/appacademy/test-first-ruby).
The 13_xml_document_spec.rb is the Rspec test that my code must pass. This test has several tasks, but it is the last one (called "indents") that my code doesn't accomplish.
Here is the Rspec test:
require "13_xml_document"
describe XmlDocument do
before do
#xml = XmlDocument.new
end
it "renders an empty tag" do
expect(#xml.hello).to eq("<hello/>")
end
it "renders a tag with attributes" do
expect(#xml.hello(:name => "dolly")).to eq('<hello name="dolly"/>')
end
it "renders a randomly named tag" do
tag_name = (1..8).map{|i| ("a".."z").to_a[rand(26)]}.join
expect(#xml.send(tag_name)).to eq("<#{tag_name}/>")
end
it "renders block with text inside" do
expect(#xml.hello { "dolly" }).to eq("<hello>dolly</hello>")
end
it "nests one level" do
expect(#xml.hello { #xml.goodbye }).to eq("<hello><goodbye/></hello>")
end
it "nests several levels" do
xml = XmlDocument.new
xml_string = xml.hello do
xml.goodbye do
xml.come_back do
xml.ok_fine(:be => "that_way")
end
end
end
expect(xml_string).to eq('<hello><goodbye><come_back><ok_fine
be="that_way"/></come_back></goodbye></hello>')
end
it "indents" do
#xml = XmlDocument.new(true)
xml_string = #xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end
expect(xml_string).to eq(
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be=\"that_way\"/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
)
end
end
And here is my code:
class XmlDocument
def initialize(indentation = false)
#indentation = indentation
#counter = 0
end
def method_missing(method, *args, &block)
hash = {}
if block
if #indentation == false
"<#{method}>#{yield}</#{method}>"
elsif #indentation == true
string = ""
string << indent1
string << "<#{method}>\n"
(###)
add_indent
string << indent1
string << yield + "\n"
sub_indent
string << indent2
string << "</#{method}\>"
string
end
elsif args[0].is_a?(Hash)
args[0].map { |key,value| "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }.join(" ")
elsif hash.empty?
"<#{method.to_s}/>"
end
end
def indent1
" " * #counter
end
def indent2
" " * #counter
end
def add_indent
#counter += 1
end
def sub_indent
#counter -= 1
end
end
This is the output I get for the "indents" part:
<hello>
<goodbye>
<come_back>
+ <ok_fine be="that_way"/>
</come_back>
</goodbye>
</hello>
Contrary to the right answer, the 4th line ('ok_fine be="that_way"/') seems be two indents closer to the left than it is supposed to be. As opposed to the rest of the lines, the 4th line is not a block, but an argument of the called method 'come_back'.
I cannot see where my mistake is. Even writing an exception in the code (where the (###) is in my code) doesn't seem to have any effect on the 4th line.
Here is the exception (###):
if args[0].is_a?(Hash)
add_indent
string << indent
arg[0].map{|key, value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>"}
end
NOTE: I assume that if I manage to give the 4th line the right numbers of indents, that also will increase the number of indents of the lines after it, so the method 'indent2' will need to be modified.
I figured out what the problem was. As I said in my question, in the Rspec test they have the following input:
xml_string = xml.hello do
xml.goodbye do
xml.come_back do
xml.ok_fine(:be => "that_way")
end
end
end
where the 4th line (xml.ok_fine(:be => "that_way")) doesn't have a block nested, but an argument. In my code I established a condition (if block) for when there is a block present and inside this first condition, a second condition (if #indentation == true) for when #indentation is true:
if block
if #indentation == false
"<#{method}>#{yield}</#{method}>"
elsif #indentation == true
...
It is inside this second condition that I create the variable 'string' where I shovel in the different parts:
elsif #indentation == true
string = ""
string << indent1
string << "<#{method}>\n"
(###)
add_indent
string << indent1
string << yield + "\n"
sub_indent
string << indent2
string << "</#{method}\>"
string
end
But because the 4th line doesn't carry a block, the first condition (if block) doesn't return true for it and therefore this 4th line is skipped.
I've re-written my code so now it passes the Rspec test:
class XmlDocument
def initialize(indentation = false)
#indentation = indentation
#counter = 0
end
def method_missing(method, args = nil, &block)
string = ""
arguments = args
if #indentation == false
if (arguments == nil) && (block == nil)
"<#{method.to_s}/>"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }.join(" ")
elsif block
"<#{method}>#{yield}</#{method}>"
end
elsif #indentation == true
if (block) || (arguments.is_a?(Hash))
string << indent1
string << "<#{method}>\n" unless !block
add_indent
string << indent1 unless !block
if block
string << yield + "\n"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }
end
sub_indent
string << indent2 unless !block
string << "</#{method}\>" unless !block
if indent2 == ""
string << "\n"
end
end
string
end
end
def indent1
" " * #counter
end
def indent2
" " * #counter
end
def add_indent
#counter += 1
end
def sub_indent
#counter -= 1
end
end
In contrast to the code I wrote in my question, in this one, the two main conditions are #indentation == false and #indentation == true and inside these two conditions I establish different exceptions for the different cases (block or no block, argument or no argument...). Specifically for elsif #indentation == true I created a condition that accepts the 4th line: if (block) || (arguments.is_a?(Hash)), or in other words, it accepts methods that have a block or an argument (especifically a a hash).
Now, I shovel in the different parts in 'string', and when I reach a block to yield there is a bifurcation:
if block
string << yield + "\n"
elsif arguments.is_a?(Hash)
arguments.map { |key,value| string << "<#{method.to_s} #{key.to_s}=\"#{value.to_s}\"/>" }
if there is a block I "yield" it, and if there is and argument that is a hash I shovel it into 'string'.
Also, there is this exception unless !block either when I indent or I shovel a method because otherwise it can introduce unwanted indents and '\n' if there is a method that doesn't have a block (as line 4th).
Finally, I had to add at the end
if indent2 == ""
string << "\n"
end
because the solution requires a '\n' at the end.
I hope this answer can help other
NOTE: I wrote a 'NOTE' in my question where I assumed I would have to modify 'indent2'. That, obviously I didn't have to do because the output I was getting did not considered the 4th line (because it doesn't have a block), so the bigger indentation (" ") of 'indent2' is all right.

Add to class value in ruby on rails data in loop

I have such code:
def accum_search
if params[:akbcap].present?
akbcap_array = [12,18,19,20,25,30,35,36,38,40,41,42,44,45,46,47,50,52,53,54,55,56,58,60,61,62,63,64,65,66,68,69,70,71,72,74,75,77,80,85,88,90,91,92,95,98,100,102,110,115,120,125,130,135,140,170,180,185,190,192,200,210,220,225]
min, max = params[:akbcap].split('-').map {|s| s.to_i }
logger.warn("!#!!!!!!!!!!!! AAA !!!!!!!!!!")
logger.warn(min)
logger.warn(max)
caprange = min...max
sa = akbcap_array.select {|n| caprange.include? n }
##cross = OtherProductsCrossList.find(:all, :conditions => {:cross_value => 1})
cap = "*"+params[:akbcap]+"*"
sa.each do |s|
logger.warn(s)
#accums = Accumulator.by_capacity(s).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
else
#accums = Accumulator.by_capacity(50).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
end
As you see i have such part:
sa.each do |s|
logger.warn(s)
#accums = Accumulator.by_capacity(s).by_size(params[:akbsize]).by_brand(params[:akbbrand])
end
but could i add on every iteration in #accums data from search? now it has last value( I could done it via arrays... but how to do via class-variable?
Yes, initiate it before the loop and use the << operator to append. End with flatten to make it a single dimension array.
#accums = []
# ...
sa.each do |s|
#accums << Accumulator.several_method_calls......
end
#accums.flatten!
or for compactness:
result = sa.map{|s| Accumulator.several_method_calls...... }.flatten

Resources