Ruby iterating through html to create hash key pairs - ruby-on-rails

I have three different string my delivery[:type] could be.
Either "Tier 1", "Tier 2" , or "Delivery"
how do i set the value of key delivery[:type] to one of those three strings.
The current way im trying to achieve it sets a nill value to the keypair but when i execute the code in binding.pry it works
stops.each do |stop|
delivery = {
customer_name: stop.css('strong.customer_name').text,
zipcode: stop.css('tr.line0,tr.line1 td').text,
date: stop.css('td[style*="text-align:right;"]').text,
# stop[:delivery_type] = ("Delivery" if stop.css('td[colspan*="4"]').text.match(/^([\w\-]+)/).to_s == "delivery"),
# stop[:delivery_type] = ("Tier 1" if stop.css('td[colspan*="4"]').text[0,5] == "Tier 1"),
# stop[:delivery_type] = ("Tier 2" if stop.css('td[colspan*="4"]').text[0,5] == "Tier 2"),
# if stop.css('td[colspan*="4"]').text.match(/^([\w\-]+)/).to_s == "delivery"
# delivery_type: "Delivery",
# else
# delivery_type: stop.css('td[colspan*="4"]').text[0,5]
# end
}
delivery[:type] = ("Delivery" if stop.css('td[colspan*="4"]').text.match(/^([\w\-]+)/).to_s == "Delivery")
delivery[:type] = ("Tier 1" if stop.css('td[colspan*="4"]').text[0,6] == "Tier 1")
delivery[:type] = ("Tier 2" if stop.css('td[colspan*="4"]').text[0,6] == "Tier 2")
binding.pry
end
stop.css('td[colspan*="4"]').text
example outputs
"Delivery   Kishan Ramineni-SO00088...   08:23 AM   FinishedW: 08:15 AM EDT  -  10:15 AM EDT A: 09:11 AM - 09:14 AM (4 mins)"
"Tier 1   Kate Abeyratne-SO000894...   09:29 AM   FinishedW: 09:15 AM EDT  -  11:15 AM EDT A: 10:08 AM - 10:11 AM (4 mins)"

Related

How can i add into database column array from ruby on rails model?

i have to following method
before_save :save_each_item_details
def save_each_item_details
items = itemname.length
i = 0
while i < items
items = itemname.length
if !ItemsCensu.exists?(itname: itemname[i], year: "#{date.to_s.split('-').first}")
ItemsCensu.create(itname: itemname[i], monadaM: mm[i], quntity: quantity[i], price: price[i], tax: tax[i], year: "#{date.to_s.split('-').first}", num_invoice << invoice_num)
i += 1
else
puts "test"
i += 1
end
end
end
num_invoice is the array from database. invoice_num is a number like 1239
Each time I save it, I want it to to add the invoice_num into num_invoice[] without removing the old value.
For example
1st save:
invoice_num = 1234
num_invoice << invoice_num
# => 1234
2nd save:
invoice_num = 12345
num_invoice << invoice_num
# => [1234, 12345]
Is there a way to build this into my ItemsCensu.create, something like ItemsCensu.create(num_invoice: << invoice_num)?

Loop two arrays at once

I do have an array with orders, each with a date. Like:
[
#<Order id: 1, date: '2019-10-07'>,
#<Order id: 2, date: '2019-10-08'>,
#<Order id: 3, date: '2019-10-10'>,
#<Order id: 4, date: '2019-10-10'>,
#<Order id: 5, date: '2019-10-12'>
]
I want to display it like this:
2019-10-05:
2019-10-06:
2019-10-07: id 1
2019-10-08: id 2
2019-10-09:
2019-10-10: id 3, id 4
2019-10-11:
2019-10-12: id 5
2019-10-13:
What is the best way to do this?
I can think of the following options:
date_range.each do ... and check if there are any corresponding orders on that date.
First sort the array of orders, then do orders.each do ... and check if there are any dates skipped.
Is there some 3rd way, that is walking through both arrays simultaneously? Like starting with the dates, when there is a corresponding order, start continue with the orders until there is a new date?
Similar to what Michael Kohl and arieljuod describe in their answers. First group your dates based on date, then loop through the dates and grab the groups that are relevant.
# mock
orders = [{id: 1, date: '2019-10-07'}, {id: 2, date: '2019-10-08'}, {id: 3, date: '2019-10-10'}, {id: 4, date: '2019-10-10'}, {id: 5, date: '2019-10-12'}]
orders.map!(&OpenStruct.method(:new))
# solution
orders = orders.group_by(&:date)
orders.default = []
date_range = Date.new(2019, 10, 5)..Date.new(2019, 10, 13)
date_range.map(&:iso8601).each do |date|
ids = orders[date].map { |order| "id: #{order.id}" }.join(', ')
puts "#{date}: #{ids}"
end
# 2019-10-05:
# 2019-10-06:
# 2019-10-07: id: 1
# 2019-10-08: id: 2
# 2019-10-09:
# 2019-10-10: id: 3, id: 4
# 2019-10-11:
# 2019-10-12: id: 5
# 2019-10-13:
#=> ["2019-10-05", "2019-10-06", "2019-10-07", "2019-10-08", "2019-10-09", "2019-10-10", "2019-10-11", "2019-10-12", "2019
I'd start with something like this:
Group the array of orders by date: lookup = orders.group_by(:date)
Iterate over your date range, use date as key into lookup, so at least you don't need to traverse the orders array repeatedly.
I would do a mix of both:
# rearange your array of hashes into a hash with [date, ids] pairs
orders_by_date = {}
orders.each do |id, date|
orders_by_date[date] ||= []
orders_by_date[date] << id
end
# iterate over the range and check if the previous hash has the key
date_range.each do |date|
date_s = date.strftime('%Y-%m-%d')
date_ids = orders_by_date.fetch(date_s, []).map { |x| "id: #{x}" }.join(', ')
puts "#{date_s}: #{date_ids}"
end
Try group_by. You can find the documentation at https://apidock.com/ruby/Enumerable/group_by
grouped_orders = orders.group_by{|ords| ords[:date]}
(start_date..end_date).each do |order_date|
puts order_date
grouped_orders.fetch(order_date).map{|m| puts m.id}
end
Data
We are given an array of instances of the class Order:
require 'date'
class Order
attr_reader :id, :date
def initialize(id,date)
#id = id
#date = date
end
end
arr = ['2019-10-07', '2019-10-08', '2019-10-10', '2019-10-10', '2019-10-12'].
map.each.with_index(1) { |s,i| Order.new(i, Date.iso8601(s)) }
#=> [#<Order:0x00005a49d68ad8b8 #id=1,
# #date=#<Date: 2019-10-07 ((2458764j,0s,0n),+0s,2299161j)>>,
# #<Order:0x00005a49d68ad6d8 #id=2,
# #date=#<Date: 2019-10-08 ((2458765j,0s,0n),+0s,2299161j)>>,
# #<Order:0x00005a49d68ad3b8 #id=3,
# #date=#<Date: 2019-10-10 ((2458767j,0s,0n),+0s,2299161j)>>,
# #<Order:0x00005a49d68ad138 #id=4,
# #date=#<Date: 2019-10-10 ((2458767j,0s,0n),+0s,2299161j)>>,
# #<Order:0x00005a49d68aceb8 #id=5,
# #date=#<Date: 2019-10-12 ((2458769j,0s,0n),+0s,2299161j)>>]
and start and end dates:
start_date = '2019-10-05'
end_date = '2019-10-13'
Assumption
I assume that:
Date.iso8601(start_date) <= arr.first.date &&
arr.first.date <= arr.last.date &&
arr.last.date <= Date.iso8601(end_date)
#=> true
There is no need for the elements of arr to be sorted by date.
Code
h = (start_date..end_date).each_with_object({}) { |d,h| h[d] = d + ':' }
arr.each do |inst|
date = inst.date.strftime('%Y-%m-%d')
h[date] += "#{h[date][-1] == ':' ? '' : ','} id #{inst.id}"
end
h.values
#=> ["2019-10-05:",
# "2019-10-06:",
# "2019-10-07: id 1",
# "2019-10-08: id 2",
# "2019-10-09:",
# "2019-10-10: id 3, id 4",
# "2019-10-11:",
# "2019-10-12: id 5",
# "2019-10-13:"]
Explanation
The first step is to construct the hash h:
h = (start_date..end_date).each_with_object({}) { |d,h| h[d] = d + ':' }
#=> {"2019-10-05"=>"2019-10-05:", "2019-10-06"=>"2019-10-06:",
# "2019-10-07"=>"2019-10-07:", "2019-10-08"=>"2019-10-08:",
# "2019-10-09"=>"2019-10-09:", "2019-10-10"=>"2019-10-10:",
# "2019-10-11"=>"2019-10-11:", "2019-10-12"=>"2019-10-12:",
# "2019-10-13"=>"2019-10-13:"}
Now we will loop through the elements inst (instances of Order) of arr, and for each will alter the value of the key in h that equals inst.date converted to a string:
arr.each do |inst|
date = inst.date.strftime('%Y-%m-%d')
h[date] += "#{h[date][-1] == ':' ? '' : ','} id #{inst.id}"
end
Resulting in:
h #=> {"2019-10-05"=>"2019-10-05:",
# "2019-10-06"=>"2019-10-06:",
# "2019-10-07"=>"2019-10-07: id 1",
# "2019-10-08"=>"2019-10-08: id 2",
# "2019-10-09"=>"2019-10-09:",
# "2019-10-10"=>"2019-10-10: id 3, id 4",
# "2019-10-11"=>"2019-10-11:",
# "2019-10-12"=>"2019-10-12: id 5",
# "2019-10-13"=>"2019-10-13:"}
All that remains is to extract the values of the hash h:
h.values
#=> ["2019-10-05:",
# "2019-10-06:",
# "2019-10-07: id 1",
# "2019-10-08: id 2",
# "2019-10-09:",
# "2019-10-10: id 3, id 4",
# "2019-10-11:",
# "2019-10-12: id 5",
# "2019-10-13:"]

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

Rails seed.db syntax

I have this code. The first and second command for rails work good, but "project.todos << todo" part ruby interpreter treats like error.
NoMethodError: undefined method `todos' for nil:NilClass
How can i fix it? When i put in seeds.rb text like this, all good, but with code it not work:
#work
project = Project.create title: "Family"
todo = Todo.create text: "Buy milk", isCompleted: true
project.todos << todo
#code
require 'yaml'
yhash = YAML.load_file(File.open("#{Rails.root}/db/seeds.yml"))
yhash.each do |key, value|
value.each do |k, v|
k.each do |ke, proj|
if proj.class.name == 'String'
project = Project.create title: proj
elsif proj.class.name == 'Array'
proj.each do |todo|
i = 0
tmp = ''
todo.each do |to|
to.each do |t|
if i == 0
i = 1
elsif i == 2
i = 3
elsif i == 1
tmp = t
i = 2
elsif i == 3
if t == false
bool = 'false'
else
bool = 'true'
end
todo = Todo.create text: tmp, isCompleted: bool
project.todos << todo
i = 0
tmp = ''
end
end
end
end
end
end
end
end
Short answer
You really should change your YAML structure, it would make everything much easier.
---
projects:
- title: Family
todos:
- text: Buy milk
is_completed: true
- text: Todo2
is_completed: false
- title: Project2
todos:
- text: Todo3
is_completed: true
- text: Todo4
is_completed: false
You could parse it in a few lines :
yaml = YAML.load(File.read('test.yaml'))
yaml['projects'].each do |project|
title = project['title']
p title
# Create project here
project['todos'].each do |todo|
p todo
# Create todo here
# Add todo to project
end
end
NoMethodError
NoMethodError: undefined method 'todos' for nil:NilClass
means that the object on which todos is called (project) isn't defined.
Indeed, project isn't defined, but proj is, so you should create a Project called project first.
Naming objects
To avoid confusion :
project_name could be a string containing the project name
project_names could be an array of project names
project could be a Project object
todos could be an Array of Todos
Your code indicates that proj is used for different purposes :
project = Project.create title: proj
proj.each do |todo|
Those 2 projs should be different objects with different variable names, and should be initialized separately.
It's easy to write nested hashes in YAML, one hash could represent Projects, the other could represent Todos.
Block variables
If you create a Project instance called project inside a loop during the first iteration, it will not be available to the next iterations :
[1, 2, 3].each do |i|
if i==1
project = "my project"
end
p i
p project
end
# 1
# "my project"
# 2
# nil
# 3
# nil
If you want a project variable to be available to all iterations, you should create it outside of the loop :
project = "my project"
[1, 2, 3].each do |i|
p i
p project
end
# 1
# "my project"
# 2
# "my project"
# 3
# "my project"
Another possibility would be to use #project instead of project :
[1,2,3].each do |i|
if i==1
#project = "my project"
end
p i
p #project
end
# 1
# "my project"
# 2
# "my project"
# 3
# "my project"
case
Instead of :
if proj.class.name == 'String'
project = Project.create title: proj
elsif proj.class.name == 'Array'
You could use :
case proj
when String
# proj is a String
when Array
# proj is an Array
end

How to skip over null values in Ruby hashes

I have an array in which each array item is a hash with date values, as shown in my example below. In actuality, it is longer and there are about 20 dates per item instead of 3. What I need to do is get the date interval values for each item (that is, how many days between each date value), and their intervals' medians. My code is as follows:
require 'csv'
require 'date'
dateArray = [{:date_one => "May 1", :date_two =>"May 5", :date_three => " "}, {:date_one => "May 10", :date_two =>"May 10", :date_three => "May 20"}, {:date_one => "May 6", :date_two =>"May 11", :date_three => "May 12"}]
public
def median
sorted = self.sort
len = sorted.length
return (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
puts dateIntervals = dateArray.map{|h| (DateTime.parse(h[:date_two]) - DateTime.parse(h[:date_one])).to_i}
puts "\nMedian: "
puts dateIntervals.median
Which returns these date interval values and this median:
4
0
5
Median: 4
However, some of these items' values are empty, as in the first item, in its :date_three value. If I try to run the same equations for the :date_three to :date_two values, as follows, it will throw an error because the last :date_three value is empty.
It's okay that I can't get that interval, but I would still would need the next two items date intervals (which would be 10 and 1).
How can I skip over intervals that return errors when I try to run them?
I would recommend adding helper functions that can deal with the types of inputs you're expecting. For instance:
def date_diff(date_one, date_two)
return nil if date_one.nil? || date_two.nil?
(date_one - date_two).to_i
end
def str_to_date(input_string)
DateTime.parse(input_string)
rescue
nil
end
dateArray.map{|h| date_diff(str_to_date(h[:date_three]), str_to_date(h[:date_two])) }
=> [nil, 10, 1]
dateArray.map{|h| date_diff(str_to_date(h[:date_three]), str_to_date(h[:date_two])) }.compact.median
=> 5.5
The bonus here is that you can then add unit tests for the individual components so that you can easily test edge cases (nil dates, empty string dates, etc).
In your map block, you can just add a check to make sure the values aren't blank
dateIntervals = dateArray.map{ |h|
(DateTime.parse(h[:date_two]) - DateTime.parse(h[:date_one])).to_i unless any_blank?(h)
}
def any_blank?(h)
h.each do |k, v|
return true if v == " "
end
end
I would first just filter out the empty values first (I check if the string consists entirely of whitespace or is empty), then compare the remaining values using your existing code. I added a loop which will compare all values in the sequence to the next value.
dateArray = [
{ date_one: "May 1", date_two: "May 5", date_three: " ", date_four: "" },
{ date_one: "May 10", date_two: "May 10", date_three: "May 20" }
]
intervals = dateArray.map do |hash|
filtered = hash.values.reject { |str| str =~ /^\s*$/ }
(0...filtered.size-1).map { |idx| (DateTime.parse(filtered[idx+1]) - DateTime.parse(filtered[idx])).to_i }
end
# => [[4], [0, 10]]

Resources