Ruby - calculations with gets.chomp in a loop - ruby-on-rails

I'm very new to Ruby. I've been trying to find a way to do calculations with user inputs. There are two things that I wanted to do:
Output which friend has the most dinosaurs or jellyfish.
Output which friend has the most dinosaurs and jellyfish combined.
I only have this loop requesting for user inputs so far:
f = "yes"
while f == "yes"
print "Enter your name: "
n = gets.chomp.to_f
print "Enter the number of dinosaurs you have: "
d = gets.chomp.to_f
print "Enter the number of jellyfish you have: "
j = gets.chomp.to_f
print "Another friend? (yes/no)"
f = gets.chomp
end
Any help is appreciated. Thank you so much!
UPDATES:
Thank you so much for you all's help. I've realized that I was not specific enough. I'll try to clarify that here.
The output that I want looks something like this (things in ~ ~ are user inputs):
Enter your name: ~ Bob~
Enter the number of dinosaur you have: ~3~
Enter the number of jellyfish you have: ~6~
Another friend? (yes/no) ~yes~
Enter your name: ~ Sally~
Enter the number of dinosaur you have: ~2~
Enter the number of jellyfish you have: ~8~
Another friend? (yes/no) ~no~
Friend who has the most dinosaurs: Bob
Friend who has the most jellyfish: Sally
Friend who has the most dinosaurs and jellyfish combined: Sally
So far, the codes that I wrote only gets me to "Another friend? (yes/no)" but I'm not sure how to ask Ruby to output the last three lines that I want. Can you folks shed some light on this, please?
Thank you very much!
MORE UPDATES:
Thanks for all of your help! Got it figured out with Hash and Array. Thank you!

The answer to your question is three-fold.
Collecting user input
gets returns a line from stdin, including the newline. If the user enters Bobenter then gets returns "Bob\n".
To strip the newline you use String#chomp:
"Bob\n".chomp #=> "Bob"
To convert an input like "3\n" to an integer 3, you can use String#to_i:
"3\n".to_i #=> 3
Note that to_i ignores extraneous characters (including newline), so you can avoid chomp.
Storing data
You probably want to store a user's data in one place. In an object-oriented programming language like Ruby, such "place" is often a class. It can be as simple as:
class User
attr_accessor :name, :dinosaurs, :jellyfish
end
attr_accessor creates getters and setters, so you can read and write a user's attributes via:
user = User.new
user.name = 'Bob'
user.name #=> "Bob"
Since you don't want just one user, you need another place to store the users.
An Array would work just fine:
users = []
user = User.new
user.name = 'Bob'
user.dinosaurs = 3
users << user
users
#=> [#<User #name="Bob", #dinosaurs=3>]
Adding a second user:
user = User.new # <- same variable name, but new object
user.name = 'Sally'
user.dinosaurs = 2
users << user
users
#=> [#<User #name="Bob", #dinosaurs=3>, #<User #name="Sally", #dinosaurs=2>]
Retrieving the users (or their attributes) from the array:
users[0] #=> #<User #name="Bob">
users[0].name #=> "Bob"
users[1].name #=> "Sally"
Calculating with data
Array includes many useful methods from the Enumerable mixin.
To get an element with a maximum value there's max_by – it passes each element (i.e. user) to a block and the block has to return the value you are interested in (e.g. dinosaurs). max_by then returns the user with the highest dinosaurs value:
users.max_by { |u| u.dinosaurs }
#=> #<User #name="Bob">
You can also calculate the value:
users.max_by { |u| u.dinosaurs + u.jellyfish }
But that would currently result in an exception, because we did not set jellyfish above.

Right now you're trying to turn the name into a float (number) by calling to_f on it. It's already a string when it's coming in from the user, and should probably stay a string.
Also, you're currently overwriting each of your variables in every iteration of your loop. So if Bob fills this out, and wants to add a friend, Sally, all of Bob's information is overwritten by Sally's.
Instead, you'll need to create a Hash or Array, then add each user to it one by one from your loop.
continue = "yes"
users = {}
while continue == "yes"
print "Enter your name: "
name = gets.chomp.to_f
print "Enter the number of dinosaurs you have: "
dinosaursCount = gets.chomp.to_f
print "Enter the number of jellyfish you have: "
jellyfishCount = gets.chomp.to_f
users[name] = {dinosaurs: dinosaursCount, jellyfish: jellyfishCount}
print "Another friend? (yes/no)"
continue = gets.chomp
end
Now if Bob and Sally have both been added through the command line, you can get their data by doing:
users.Bob #{dinosaurs: 10, jellyfish: 10}
users.Bob.dinosaurs #10
users.Bob.jellyfish #10

Related

Rails csv row from parent record with many child associations

I have a members table from which I pull :member_id, :client_uid, :first_name, and :last_name.
The Members table has a has_many association to Quality Measures from which I pull :measure and :measure_status for each association and flatten it.
I am trying to get the desired Members data and the flattened Quality Measure data into one CSV row.
Since there can be any number of Quality Measure associated records, I get the max number of associated records and create that number of Measure and Measure Status columns and create the proper number of headers:
max_qm_associations = get_max_qm_associations(filtered_members_ids)
column_headers = ["Member ID", "Client UID","First Name", "Last Name"]
max_qm_associations.times do |qm|
column_headers += ["Measure", "Measure Status"]
end
so that the desired output appears like the following assuming the max number of Quality Measure associated records for my Members is 2. Therefore:
Member ID
Client UID
First Name
Last Name
Measure
Measure Status
Measure
Measure Status
1
23232
John
Doe
Drink Coffee
Met
Eat
Not Met
2
32323
Jane
Doe
Walk
Met
none
none
So far I've tried many things. My most recent looks nice enough (but not the goal) by putting the Measure - Measure Status in the first Measure column:
csv = CSV.generate(headers: true) do |csv|
# Add headers
csv << column_headers
filtered_members.each do |member|
# Add member data
qm_measures = ""
member.quality_measures.each do |qm|
qm_measures << [qm.measure, qm.measure_status.upcase].join(' - ') + "\n"
end
csv << [member.id, member.client_uid, member.first_name.humanize, member.last_name.humanize, qm_measures]
end
end
The result:
Member ID
Client UID
First Name
Last Name
Measure
Measure Status
Measure
Measure Status
1
23232
John
Doe
Drink Coffee - Met \n Eat - Not Met
2
32323
Jane
Doe
Walk - Met \n
Another attempt simply did the above but only as comma separated array values:
csv = CSV.generate(headers: true) do |csv|
# Add headers
csv << column_headers
filtered_members.each do |member|
# Add member data
csv << [member.id, member.client_uid, member.first_name.humanize, member.last_name.humanize, member.quality_measures.map {|qm| "#{qm.measure}, #{qm.measure_status}"}.flatten]
end
end
This result:
Member ID
Client UID
First Name
Last Name
Measure
Measure Status
Measure
Measure Status
1
23232
John
Doe
"Drink Coffee,Met", "Eat,Not Met"
2
32323
Jane
Doe
"Walk,Met"
Again, as a refresher, my desired output is as follows:
Member ID
Client UID
First Name
Last Name
Measure
Measure Status
Measure
Measure Status
1
23232
John
Doe
Drink Coffee
Met
Eat
Not Met
2
32323
Jane
Doe
Walk
Met
none
none
Edits: Table formats keep breaking on submit.
The problem seems to come from this part member.quality_measures.map {|qm| "#{qm.measure}, #{qm.measure_status}"}.flatten
it returns you the following array ["Drink Coffee,Met", "Eat,Not Met"]
You would probably want to do this in your code:
member.quality_measures.map{|qm| "#{qm.measure}", "#{qm.measure_status}"}.flatten
that will return the ["Drink Coffee","Met", "Eat","Not Met"]
and then you would possibly want to flatten the whole array that you append like so:
csv << [member.id, member.client_uid, member.first_name.humanize, member.last_name.humanize, member.quality_measures.map {|qm| "#{qm.measure}", "#{qm.measure_status}"}.flatten].flatten
I did not test it yet but it should work if you go in that direction.
Thanks to #iseitz!
I was only one .flatten away from my desired output.
csv = CSV.generate(headers: true) do |csv|
# Add headers
csv << column_headers
filtered_members.each do |member|
qms = member.quality_measures.map {|qm| ["#{qm.measure}", "#{qm.measure_status}"]}.flatten
csv << [member.id, member.client_uid, member.first_name.humanize, member.last_name.humanize, qms].flatten
end
end

Rails set filter by the current user - joins association issue

I have a filter I'm using for a form that I'd like to only show those that have an email that matches the current user.
Attributes involved: Users' email, Career's recipient, Region name
Careers belongs_to :region and I'm currently displaying Region with the following in my view:
-#careers.each do |career|
th =career.region&.name
So the logic would be to compare current_user.email against all Careers.recipienct and if present then display only those Regions that are represented.
Example would be:
Region | Career | Recipients
Pacific Northwest | Data Analyst | john#doe.com, jill#excite.com
So I know it needs to hit my region select which looks like:
= select_tag :region_id,
options_from_collection_for_select(sort_region, :id, :name, params[:region_id]),
include_blank: 'All Regions'
sort_region currently has:
def sort_region
Region.where.not(name: ['HQ', 'Canada'].sort_by(&:position)
end
So my thought was to tackle the comparison in something similar with:
def user_region
if current_user.super_admin?
return sort_region
else
arr = []
Career.all.each do |emails|
arr << emails.recipient
end
if arr.include?(current_user.email)
return BLANK
end
end
end
The blank is where I'm stuck. I only want to show those Regions where there is a match of the Career.recipient with the current_user.email.
I wrote the following for the return that basically I hate
return Region.joins(:career).where("region.name = career.region&.name")
This actually returns
Can't join 'Region' to association named 'career'
Same issue when I try
return Region.joins(:career).where("careers.region_id = region.id")
Is there a correct joins association I should be using here?
EDIT:
I think I could address the return if I could figure out how to push to the array a second value with the email.
So something like:
arr = []
Career.all.each do |emails|
arr << emails.recipient
arr << emails.region_id
end
if arr.include?(current_user.email)
return region_id
end
So this doesn't create a pair like I'd hope/want of [["john#doe.com", 2], ["jane#excite.com", 3]] but instead ["john#doe.com", 2, "jane#excite.com", 3]. Also I'm not able to do a return on region_id. So I'd need to access the integers only.
Maybe I should be creating a hash of all the items and then pull from there?
Another EDIT:
Tried putting the conditional within the iteration.
arr = []
Career.all.each do |emails|
if emails.recipient == current_user.email
arr << emails.region_id
end
end
This however shows the arr containing [1] which is not the associated region_id.
Third EDIT:
My latest attempt has been to see about doing a join where I simply pull out those Regions where Career Recipient equals the current_user.email.
Region.find(:all, joins: ' JOIN regions ON careers.region_id', conditions: "#{current_user.email} = careers.recipient")
This results in The Region you were looking for could not be found.
Arel_table for the win
Region.joins(:careers).where(Career.arel_table[:recipient].matches("%#{current_user.email}%"))

How do I randomly return an element from an array in Ruby

I will like to select a name randomly from an array list then display either in Capital or Lowercase
For example names= ["James", "John", "Jane"]
Output should be: JOHN or john or jane or JAMES or JANE or james
Please help!
I have tried using the .sample() command which selects from an array. Also, I'm aware of the .upcase() and .lowercase(), the problem now is how to combine these whole methods together in order to get the desire result which is to select a name randomly for the array list then display in either Capital or Lowercase.
def random_case(*names)
names= ["James", "John", "Jane"]
names.sample { |names| names.upcase names.downcase }
puts "Hello #{names}! How are you today?"
end
random_case()
I expect the output to be JOHN or john or jane or JAMES or JANE or james - randomly
I'd write two methods. One for returning a random name out of a list of names:
def random_name(*names)
names.sample
end
and another one for changing a given name's case: (rand < 0.5 has a 50% chance of being true)
def random_case(name)
if rand < 0.5
name.upcase
else
name.downcase
end
end
Then combine both:
5.times do
puts random_case(random_name("James", "John", "Jane"))
end
Output:
JOHN
JOHN
jane
JAMES
james
If you don't like the rand < 0.5 approach, you could also put both variants into an array and pick one randomly:
def random_case(name)
[name.upcase, name.downcase].sample
end
Or you could put the method names :upcase and :downcase in an array, pick one of them randomly and use public_send to invoke that method:
def random_case(name)
name.public_send([:upcase, :downcase].sample)
end
I would start with:
def random_case(*names)
name = names.sample # pick a random name
[true, false].sample ? name.upcase : name.downcase # return name with random format
end
random_case("James", "John", "Jane")
#=> "JAMES"
random_case("James", "John", "Jane")
#=> "john"
Try this
def random_case(names)
names.flat_map{|name| [name.upcase, name.downcase, name.capitalize]}.sample
end
names= ["James", "John", "Jane"]
puts random_case names
I think the code is quite simple to understand. Any trouble? leave a comment to let me know
You can do like this.
names= ["James", "John", "Jane"]
random = [names.sample.upcase, names.sample.downcase].sample
puts "Hello #{random}! How are you today?"

Ruby: Verifying if the given user input is a number

I want to check if the variable which is basically a user input is a 10 digit phone number or not.
There are 2 sets of validations:
- If num is less than 10 digit then prompt a msg
- if num is a string instead rather than integer
#phone = params[:phone_num]
puts "phone_num: #{#phone}"
if #phone.is_a? Integer
puts "phone_num is int"
if #phone.to_s.length == 10
puts "10 digit"
perform(#phone)
#output = "Valid Number, will receive a call"
end
else
puts "Wont be calling"
#output = "The number is invalid"
end
The output that I get is always The number is invalid no matter what I enter in text box. There are many stack overflow answering dealing with different questions but wondering why my code didn't work.
There is standard validation (length) & (numericality) for this:
#app/models/user.rb
class User < ActiveRecord::Base
validates :phone_num, length: { is: 10 }, numericality: { only_integer: true }
end
This type of validation belongs in the model.
Notes
Your controller will look as follows:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new user_params
#user.save #-> validations handled by model
end
end
There's a principle called fat model, skinny controller - you should put "data" logic in your model.
The reason for this is to remove inefficient code from the controller.
It gives you the ability to delegate much of your logic to the Rails core helpers (validations for example), instead of calling your own mass of code in the front-end (like you're doing).
Each time you run a Rails app, the various classes (controller & model) are loaded into memory. Along with all of the Rails classes (ActiveRecord etc), your controllers & models have to be loaded, too.
Any extra code causes causes bloat, making your application buggy & unusable. The best developers know when to use their own code, and when to delegate to Rails. This example is a perfect demonstration of when to delegate.
The output that I get is always The number is invalid no matter what I
enter in text box.
The reason why your code always falls back to else part because the values that are coming from the params will always be strings. So the value of params[:phone_num] is a string. So your code is failing here if #phone.is_a? Integer. Instead you need change it to params[:phone_num].to_i
#phone = params[:phone_num].to_i
puts "phone_num: #{#phone}"
if #phone.is_a? Integer
puts "phone_num is int"
if #phone.to_s.length == 10
puts "10 digit"
perform(#phone)
#output = "Valid Number, will receive a call"
end
else
puts "Wont be calling"
#output = "The number is invalid"
end
Note:
Yes. This is poor way to perform validations. I'm just answering the OP's question.
Take a look at this - A comprehensive regex for phone number validation - how to determine a string looks like a phone number. There's a very complex regex, because people have various forms for entering phone numbers!
I personally don't like super complex regexes, but it's pretty much what they were invented for. So this is when you want to figure out what sorts of forms are acceptable, write some tests, and make your code pass to your acceptance based on the massive link above!
edit: your code is wrong in a bunch of places; params are already a string, so try this! Remember your nested if/else/end, too.
#phone = params[:phone_num]
if #phone =~ /\A\d+\Z/ # replace with better regex
# this just means "string is all numbers"
puts "phone_num is int"
if #phone.length == 10
puts "10 digit"
perform(#phone)
#output = "Valid Number, will receive a call"
else
puts "Number length wrong, #{#phone.length}"
end
else
puts "Wont be calling, not a number: #{#phone.inspect}"
#output = "The number is invalid"
end

ruby on rails concatenating two active records objects

I have two complex rails (AR) queries coming from two different methods that I sometimes want to concatenate. The data structure returned in each object is the same I just want to append one onto another.
Here's a simplified example (not my actual code):
#peep1 = Person.find(1)
#peep2 = Person.find(2)
Thought something like this would work:
#peeps = #peep1 << #peep2
or this
#peeps = #peep1 + #peep2
The above is just a simplified example - joining the queries etc won't work in my case.
Edit:
Maybe concatenating is the wrong term.
Here's the output I'd like:
Say #peep1 has:
first_name: Bob
last_name: Smith
and #peep2 has:
first_name: Joe
last_name: Johnson
I want these to be combined into a third object. So if I iterate through #peeps it will contain the data from both previous objects:
#peeps has:
first_name: Bob
last_name: Smith
first_name: Joe
last_name: Johnson
Thanks!
To be frank, nothing that you are describing makes any sense :)
#peep1 and #peep2 each represent a single object -- a single row in the database.
There is no sense in which they can be meaningfully combined.
You can make an array of both of them.
#all_peeps = [#peep1, #peep2]
And then iterate over that.
#all_peeps.each do |peep|
print peep.first_name
end
This worked for me:
> #loop_feed = #job.bids.all
> #bidadd = []
> #loop_feed.each do |loop_feed|
> compare_id = loop_feed.user_id
> #user_search.each do |user|
> if compare_id == user.id
> #bidadd = [#bidadd, loop_feed].flatten
> end
> end
> end

Resources