Checking if input == Integer/float? using irb - ruby-on-rails

How to check if users didn't put an integer in if statement in a loop. I want the if statement to check if the user didn't put an integer, the system gonna say "please put an integer" Right now, everything works fine, but when user input a decimal number, the program will convert it to an integer. I want the program to say "you need to put an integer" when user put a decimal number.
I tried if soap.match(/^[\d]+$/).nil? and other methods, but didn't work. I think I need to change some code in 'if (soap_type = SOAPS[soap.to_i])', but I don't know how to change it.
SOAPS = {
1 => 'face soap',
2 => 'bar soap',
3 => 'shave soap',
4 => 'shampoo soap'
}.freeze
loop do
puts "What type of soaps do you want? (#{SOAPS.map { |k, v| "#{k} - #{v}" }.join(', ')}) Please put a number from 1 - 4 "
soap = gets
if (soap_type = SOAPS[soap.to_i])
puts "Great! You want #{soap_type}."
break
elsif !soap.match (/\A[-+]?[0-9]*\.?[0-9]+\Z/)
puts "You didn't enter an integer, please put an integer from 1-4"
else
puts "#{soap.inspect} is not a valid integer, please try again."
end
end
I hope when the user input a decimal number, the program gonna say You didn't enter an integer, please put an integer.

There's a much more Ruby way to express this, the idiomatic form if you will:
loop do
puts "What type of soaps do you want? (1 = face soap, 2 = bar soap, 3 = shave soap, 4 = shampoo soap) Please put a number from 1 - 4 "
case (soap = gets)
when '1'
puts "Great! You want face soap."
when '2'
puts "Great! You want bar soap"
when '3'
puts "Great! You want shave soap."
when '4'
puts "Great! You want shampoo soap."
else
puts "#{soap.inspect} is not a valid entry, please try again."
end
end
Where the case statement can do a ton of work for you. Here the string values are being tested instead of bothering to convert since that doesn't really matter.
Notice there's still a lot of duplication though. This is where a look-up table can be the key to simplifying things:
SOAPS = {
1 => 'face soap',
2 => 'bar soap',
3 => 'shave soap',
4 => 'shampoo soap'
}.freeze
loop do
puts "What type of soaps do you want? (#{SOAPS.map { |k, v| "#{k} - #{v}" }.join(', ')}) Please put a number from 1 - 4 "
soap = gets
if (soap_type = SOAPS[soap.to_i])
puts "Great! You want #{soap_type}."
else
puts "#{soap.inspect} is not a valid entry, please try again."
end
end
Where now you can easily add and remove entires and everything updates accordingly. This is a data-driven approach and it's what Ruby excels at because once you have something expressed in terms of data, you can build on that with a series of transformations to get the desired result.

Related

How to store data from an each where rows has the same id

I'm a newbie in rails development, i'm sorry if i can't express myself well.
I've a rails each cycle that do:
r.round_matches.each do |m|
m.round_matches_team.each do |mt|
sheet.add_row [m.round_id, mt.team_name]
end
end
Every round_match has :round_id doubled
The output is:
round_id: 2 team_name: TEST A
round_id: 2 team_name: TEST B
How i can group round by id in the each cycle and estrapolate the team_name from round_match_teams for every same round_id? I would like that my output will be:
round_id: 2 team_name[1]: TEST A team_name[2]: TEST B
This should work
r.round_matches.each do |m|
team_names = m.round_matches_team.map.with_index do |team, index|
"team_name[#{index + 1}]: #{team.team_name}"
end.join(' ')
sheet.add_row ["round_id: #{m.round_id} #{team_names}"]
end
I would handle this a little differently: I would manipulate the data to be in a better format, and create the sheet from that data.
sheet_data = Hash.new([])
r.round_matches.each do |m|
m.round_matches_team.each do |mt|
sheet_data[mt.round_id] << mt.team_name
end
end
sheet_data.each do |round_id, teams|
sheet.add_row [round_id, *teams]
end
Explained: I will generate a hash with as key the round_id and as value an array containing the collected team-names. Then when adding the row, I use the splat-operator (*) to make sure each team-name will get a separate column.
You could even sort the team-names if this might make more sense before using the splat, or instead of using *teams, use something like teams.sort.join(", ") to combine all teams into one column (if wanted/preferred).

Rails apply a condition within a sort_by looking for value

I'm working on sorting a list of employees by their title with the following:
TAGS = {
'Region Manager' => 1,
'Region Sales Manager' => 2,
'General Manager' => 3,
'Residential Sales Manager' => 4,
'Commercial Sales Manager' => 5,
'Other' => 6
}.freeze
def sorting_by_title(employees)
employees.sort_by do |x|
TAGS[x[:title]]
end
end
Works fine but...I also need to do an additional sort if an employee last name is Smith and needs to go before other individuals.
So I've tried doing something like:
return TAGS[x[:title]] unless x.last_name == "Smith"
Not working. It's erroring out on the show page with undefined method `each' for 2:Integer.
So my thought was I would build out another method to look for the last name.
def nepotism(employees)
employees.group_by do |emp|
case emp.last_name
when /Smith/ then :moocher
end
end
end
So I tried then referencing it like:
return TAGS[x[:title]] unless x.nepotism
return TAGS[x[:title]] unless x.moocher
Neither of those work. Nepotism ends up with undefined method `nepotism' for # and Moocher ends up with the same. Then I realized a simple query would work a bit better:
def nepotism
#nepotism = Employee.where(last_name: "Smith")
end
Is there a better way to sort_by a last_name if it matches Smith and THEN by the tags?
Here's a nice trick: in ruby you can compare arrays. And, consequently, use them as value in sort_by. They are compared element by element. If ary1[0] < ary2[0], then ary1 will be less than ary2, no matter the rest of the elements.
employees.sort_by do |x|
[
x.last_name == "Smith" ? 0 : 1, # all zeroes come before all ones
TAGS[x[:title]] # your main ordering parameter
]
end
This would work very well, if there were many Smiths and you needed to sort them by titles between themselves. If there's only one Smith, then #Björn's solution is simpler.
Combine them like this
employees.sort_by do |x|
x.last_name == "Smith" ? 0 : TAGS[x[:title]]
end
You can do it in the database as well (assuming Postgresql here)
def nepotism
tagstring = "array_position(ARRAY"+TAGS.keys.to_s.gsub(/\"/,"'")+", last_name)"
#nepotism = Employee.order("last_name = 'Smith' desc, " + tagstring)
end

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

creating dynamic hash with key and value in ruby on rails

I am trying to create Hash with dynamic key and respective values. For example like this
hash = {1 => 23.67, 1 => 78.44, 3 => 66.33, 12 => 44.2}
Something like this in which 1,2,12 are array index. I hope it is understandable. I am trying with the syntax from ROR tutorials.
Like this
test = Hash.new
for i in 0..23
if (s.duration.start.hour == array[i].hour)
s.sgs.each do |s1|
case s1.type.to_s
when 'M'
test ={i => s1.power} # here I am trying to create hash like give example in which i is for loop value
when 'L'
puts "to be done done"
else
puts "Not Found"
end
end
end
end
end
Updated code
test = Hash.new
for i in 0..23
if (s.duration.start.hour == array[i].hour)
s.sgs.each do |s1|
case s.type.to_s
when 'M'
puts s1.power;
test[i] = s1._power
when 'L'
puts "to be done"
else
puts "Not Found"
end
end
end
end
Results
on traversing
for t in 0..array.size
puts test[t]
end
Results :
t = 68.6 # which is last value
and expected
t = 33.4
t = 45.6 etc
Sample logs
after assign {23=>#<BigDecimal:7f3a1e9a6870,'0.3E2',9(18)>}
before assign {23=>#<BigDecimal:7f3a1e9a6870,'0.2E2',9(18)>}
after assign {23=>#<BigDecimal:7f3a1e9ce550,'-0.57E2',9(18)>}
before assign {23=>#<BigDecimal:7f3a1e9ce550,'-0.57E2',9(18)>}
if any other optimised solution is there would be good thanks
You are re-assigning test with a new hash on each iteration. You should add to it, so instead of
test ={i => s1.power}
you should do:
test[i] = s1.power
This sets the value of key i to s1.power
If you want to keep an array of all the values for a given key, I would suggest the following (more ruby-ish) solution:
hour_idx = array.find_index { |item| s.duration.start.hour == item.hour }
values = case s.type.to_s
when 'M'
s.sgs.map(&:_power)
when 'L'
puts "to be done"
else
puts "Not Found"
end
test = { hour_idx => values }
What I'm doing here is:
Find the hour_idx which is relevant to the current s (I assume there is only one such item)
Create an array of all the relevant values according to s.type (if it is 'M' an array of all the _power of s.sgs, for 'L' whatever map you need, and nil otherwise)
Create the target hash using the values set in #1 and #2...

How to pluralize "There is/are N object/objects"?

Pluralizing a single word is simple:
pluralize(#total_users, "user")
But what if I want to print "There is/are N user/users":
There are 0 users
There is 1 user
There are 2 users
, i.e., how to pluralize a sentence?
You can add a custom inflection for it. By default, Rails will add an inflections.rb to config/initializers. There you can add:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular "is", "are"
end
You will then be able to use pluralize(#total_users, "is") to return is/are using the same rules as user/users.
EDIT: You clarified the question on how to pluralize a sentence. This is much more difficult to do generically, but if you want to do it, you'll have to dive into NLP.
As the comment suggests, you could do something with I18n if you just want to do it with a few sentences, you could build something like this:
def pluralize_sentence(count, i18n_id, plural_i18n_id = nil)
if count == 1
I18n.t(i18n_id, :count => count)
else
I18n.t(plural_i18n_id || (i18n_id + "_plural"), :count => count)
end
end
pluralize_sentence(#total_users, "user_count")
And in config/locales/en.yml:
en:
user_count: "There is %{count} user."
user_count_plural: "There are %{count} users."
This is probably best covered by the Rails i18n pluralization features. Adapted from http://guides.rubyonrails.org/i18n.html#pluralization
I18n.backend.store_translations :en, :user_msg => {
:one => 'There is 1 user',
:other => 'There are %{count} users'
}
I18n.translate :user_msg, :count => 2
# => 'There are 2 users'
I think the first part of Martin Gordon's answer is pretty good.
Alternatively, it's kind of messy but you can always just write the logic yourself:
"There #{#users.size == 1 ? 'is' : 'are'} #{#users.size} user#{'s' unless #users.size == 1}."
UPDATE to code: I no longer use the inflections route as stated in #Martin Gordon's answer. For some reason it would cause other non-related functions to error. I did extensive tests to confirm, though could not track down a reason why. So, below is now what I use and it works.
There are many ways to do this. This is how I did it using Rails 6.0.3.4 and Ruby 2.7.1.
I wanted to pluralize this sentence:
Singular: There is 1 private group
Plural: There are 2 private groups
What I did is I went to application_helper.rb and added this code:
def pluralize_private_statement(list, word)
num_in_list = list.count
is_or_are = num_in_list == 1 ? 'is' : 'are'
return "There " + is_or_are + " " + num_in_list.to_s + " private " + word.pluralize(num_in_list)
end
Now, all I have to use in my view is:
<%= pluralize_private_statement(private_groups, "group") %>
# private_groups = 2
# -> There are 2 private groups
What the code in application_helper.rb does is first create a variable for the number of items in the list passed and store it in num_in_list. Then it creates a second varible checking if the num_in_list is equal to 1 and if so returns 'is' otherwise it returns 'are'. Then, it returns the sentence that is constructed with the information obtained.
The first part of the sentence is a simple string, then the is_or_are variable which holds either 'is' or 'are' as explained above. Then it adds a space with the number of list items, converted from an integer to a string, followed by the 'private' word. Then it adds the pluralization of the word passed to the initial function; but, only returns the singular/plural word without a number attached as pluralize(#total_users, "is") would do.
Here is how you could use it for your specific question.
First, add this to your application_helper.rb file:
def pluralize_sentence(list, word)
num_in_list = list.count
is_or_are = num_in_list == 1 ? 'is' : 'are'
return "There " + is_or_are + " " + num_in_list.to_s + " " + word.pluralize(num_in_list)
end
Lastly, you can use this code wherever you wish to have the pluralized sentence:
<%= pluralize_sentence(#total_users, "user") %>
Happy Coding!

Resources