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
Related
Originally asked this question about Regex for Usernames: Usernames that cannot start or end with characters
How can I achieve this with correct syntax for Ruby on Rails?
Here's my current User.rb validation:
validates_format_of :username, with: /\A[\w\._]{3,28}\z/i
This validation allows underscores and periods, but the goal is to not allow them at the start or end of a username.
I'm trying to achieve these rules with Rails regex:
Can contain lowercase and uppercase letters and numbers
Can contain underscores and periods
Cannot contain 2 underscores in a row
Cannot contain 2 periods in a row
Cannot begin or end with an underscore or period
Cannot contain letters with accents
Must be between 3 and 28 letters in length
Valid:
Spicy_Pizza
97Indigos
Infinity.Beyond
Invalid:
_yahoo
powerup.
un__real
no..way
You may use
/\A(?=.{3,28}\z)[a-zA-Z0-9]+(?:[._][a-zA-Z0-9]+)*\z/
See the Rubular demo.
Details
\A - start of string
(?=.{3,28}\z) - 3 to 28 chars other than line break chars up to the end of the string are allowed/required
[a-zA-Z0-9]+ - one or more ASCII letters / digits
(?:[._][a-zA-Z0-9]+)* - 0+ sequences of:
[._] - a . or _
[a-zA-Z0-9]+ - one or more ASCII letters / digits
\z - end of string.
Although totally possible, as Wiktor's answer shows, my recommendation would be to not define this in a single regular expression, since:
The solution is quite confusing to understand, unless you know regular expressions quite well.
Similarly, the solution is quite difficult to update with new requirements, unless you understand regular expressions quite well.
By performing this entire check in one go, if a validation fails then you'll inevitably end up with one generic error message, e.g. "Invalid Format", which does not explain why it's invalid. The exercise is then left to the user to re-read the nontrivial format rules and understand why.
Instead, I would recommend defining a custom validation class, which can perform each of these checks separately (via easy to understand methods), and add a different error message upon each check failing.
Something along the lines of:
# app/models/user.rb
class User < ApplicationRecord
validates :username, presence: true, username: true
end
# app/validators/username_validator.rb
class UsernameValidator < ActiveModel::EachValidator
def validate(record, attribute, value)
validate_length(record, attribute, value)
validate_allowed_chars(record, attribute, value)
validate_sequential_chars(record, attribute, value)
validate_first_and_last_chars(record, attribute, value)
end
private
def validate_length(record, attribute, value)
unless value.length >= 3 && value.length <= 28
record.errors[attribute] << "must be between 3 and 28 characters long"
end
end
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-zA-Z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z, A-Z or 0-9"
end
end
def validate_sequential_chars(record, attribute, value)
if value =~ /[._]{2}/
record.errors[attribute] << "cannot contain two consecutive periods or underscores"
end
end
def validate_first_and_last_chars(record, attribute, value)
if value =~ /\A[._]/ || value =~ /[._]\z/
record.errors[attribute] << "cannot start/end with a period or underscore"
end
end
end
So for instance, you asked above: "What if I needed to extend this to allow lowercase letters only?" I think it's now quite obvious how the code could be updated to accommodate such behaviour, but to be clear - all you'd need to do is:
def validate_allowed_chars(record, attribute, value)
unless value =~ /\A[._a-z0-9]*\z/
record.errors[attribute] << "must only contain periods, underscores, a-z or 0-9"
end
end
You could also now, quite easily, write tests for these validation checks, and assert that the correct validation is being performed by verifying against the contents of the error message; something that is not possible when all validation failures result in the same error,
Another benefit to this approach is that the code can easily be shared (perhaps with some slight behavioural differences). You could perform the same validation on multiple attributes, or multiple models, perhaps with different allowed lengths or formats.
I have a Model user with the following method:
def number_with_hyphen
number&.insert(8, "-")
end
When I run it several times in my tests I get the following output:
users(:default).number_with_hyphen
"340909-1234"
(byebug) users(:default).number_with_hyphen
"340909--1234"
(byebug) users(:default).number_with_hyphen
"340909---1234"
(byebug) users(:default).number_with_hyphen
"340909----1234"
It changes the number ?Here are the docs https://apidock.com/ruby/v1_9_3_392/String/insert
When I restructure my method to:
def number_with_hyphen
"#{number}".insert(8, "-") if number
end
If works like expected. The output stays the same!
How would you structure the code, how would you perform the insert?
which method should I use instead. Thanks
If you're using the insert method, which in the documentation explicitly states "modifies str", then you will need to avoid doing this twice, rendering it idempotent, or use another method that doesn't mangle data.
One way is a simple regular expression to extract the components you're interested in, ignoring any dash already present:
def number_with_hyphen
if (m = number.match(/\A(\d{8})\-?(\d+)\z/))
[ m[1], m[2] ].join('-')
else
number
end
end
That ends up being really safe. If modified to accept an argument, you can test this:
number = '123456781234'
number_with_hyphen(number)
# => "12345678-1234"
number
# => "123456781234"
number_with_hyphen(number_with_hyphen(number))
# => "12345678-1234"
number_with_hyphen('1234')
# => "1234"
Calling it twice doesn't mangle anything, and any non-conforming data is sent through as-is.
Do a clone of the string:
"#{number}".clone.insert(8, '-')
Hi i had created a small ruby project which consists of JSON file. I stored the JSON data into hash keys. AND worte a method to access the data which is present in hash key using user input. But when i try to send use the user input i am getting this error
how_many_ingredients': undefined methodkeys' for nil:NilClass (NoMethodError)
I found this link with same question and tried that solution but still i'm getting the same error
Accessing Hash Keys with user inputted variables, NoMethodError
File one where all the methods are written
require 'json'
class Methods
attr_accessor :name, :text
def initilize(name)
#name = name
#text = text
end
def how_many_ingredients(text)
puts 'text'
file = File.read('a.json')
hash = JSON.parse(file)
#puts hash['recipes']['pizza'].keys
puts hash['recipes'][text].keys
end
end
File 2 where how_Many_ingredients method is accessed, I can see that the variable is passed to that method
require './1'
class Hello < Methods
person = Methods.new
person.test
puts "enter recipie"
person.name
str = gets
person.how_many_ingredients str
end
Note that when you use gets, the input can contain newline and carriage return characters. You'll need to use gets.chomp to filter these. This is likely the cause of the issue in your program.
Compare the following two:
> puts gets.size
"Hello!"
# 7
> puts gets.chomp.size
"Hello!"
# 6
Note that you'll still need to extend your program to account for user inputted keys that are not in your hash.
Your code assumes that there will always be a hash stored at hash['recipes'][text] - you need to cater to the cases where it isn't.
A simple way to do this is to work your way down through the hash with && symbols - if any step is nil (or false), the line will return nil (or false) rather than exploding. eg
puts hash['recipes'] && hash['recipes'][text].is_a?(Hash) && hash['recipes'][text].keys
Note i'm testing that hash['recipes'][text] is a hash (rather than just a string for example) before calling .keys on it.
Often I need to check if some value is blank and write that "No data present" like that:
#user.address.blank? ? "We don't know user's address" : #user.address
And when we have got about 20-30 fields that we need to process this way it becomes ugly.
What I've made is extended String class with or method
class String
def or(what)
self.strip.blank? ? what : self
end
end
#user.address.or("We don't know user's address")
Now it is looking better. But it is still raw and rough
How it would be better to solve my problem. Maybe it would be better to extend ActiveSupport class or use helper method or mixins or anything else. What ruby idealogy, your experience and best practices can tell to me.
ActiveSupport adds a presence method to all objects that returns its receiver if present? (the opposite of blank?), and nil otherwise.
Example:
host = config[:host].presence || 'localhost'
Phrogz sort of gave me the idea in PofMagicfingers comment, but what about overriding | instead?
class String
def |(what)
self.strip.blank? ? what : self
end
end
#user.address | "We don't know user's address"
Since you're doing this in Ruby on Rails, it looks like you're working with a model. If you wanted a reasonable default value everywhere in your app, you could (for example) override the address method for your User model.
I don't know ActiveRecord well enough to provide good code for this; in Sequel it would be something like:
class User < Sequel::Model
def address
if (val=self[:address]).empty?
"We don't know user's address"
else
val
end
end
end
...but for the example above this seems like you'd be mixing view logic into your model, which is not a good idea.
Your or method might have some unwanted side-effects, since the alternative (default) value is always evaluated, even if the string is not empty.
For example
#user.address.or User.make_a_long_and_painful_SQL_query_here
would make extra work even if address is not empty. Maybe you could update that a bit (sorry about confusing one-liner, trying to keep it short):
class String
def or what = ""
self.strip.empty? ? block_given? ? yield : what : self
end
end
#user.address.or "We don't know user's address"
#user.address.or { User.make_a_long_and_painful_SQL_query_here }
It is probably better to extend ActiveRecord or individual models instead of String.
In your view, you might prefer a more explicit pattern like
#user.attr_or_default :address, "We don't know the user's address"
Ruby:
unless my_str.empty? then my_str else 'default' end
RoR:
unless my_str.blank? then my_str else 'default' end
I recommend to use options.fetch(:myOption, defaultValue) because it works great with boolean flags like the ones mentioned above and therefore seems better to use in general.
Examples
value = {}
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = false
puts !!(value.fetch(:condition, true)) # Print false
value = {}
value[:condition] = true
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = nil
puts !!(value.fetch(:condition, true)) # Print false
I'm trying to remove the commas from a field in a model. I want the user to type a number, i.e. 10,000 and that number should be stored in the database as 10000. I was hoping that I could do some model-side normalization to remove the comma. I don't want to depend on the view or controller to properly format my data.
I tried:
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
no worky.
http://github.com/mdeering/attribute_normalizer looks like a promising solution to this common problem. Here are a few examples from the home page:
# By default it will strip leading and trailing whitespace
# and set to nil if blank.
normalize_attributes :author, :publisher
# Using one of our predefined normalizers.
normalize_attribute :price, :with => :currency
# You can also define your normalization block inline.
normalize_attribute :title do |value|
value.is_a?(String) ? value.titleize.strip : value
end
So in your case you might do something like this:
normalize_attribute :title do |value|
value.to_s.gsub(',', '')
end
I think you're doing it right. This test passes:
test "should remove commas from thenumber" do
f = Foo.new(:thenumber => "10,000")
f.save
f = Foo.find(f.id)
assert f.thenumber == "10000"
end
And I used your code.
class Foo < ActiveRecord::Base
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end
end
Now, my schema is set up for thenumber to be a string though, not an integer.
Started
.
Finished in 0.049666 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
If you wanted to store this in the db as an integer, then you definitely need to override the setter:
def thenumber=(value)
self['thenumber'] = value.to_s.gsub(',','').to_i
end
If you do it your way, with an integer column, it gets truncated by AR....
>> f.thenumber = "10,000"
=> "10,000"
>> f.thenumber
=> 10
That's a little-known thing with Ruby and integers... it auto-casts by truncating anything that's no longer an integer.
irb(main):004:0> i = "155-brian-hogan".to_i
=> 155
Can be cool for things like
/users/155-brian-hogan
#user = User.find_by_id(params[:id])
But not so cool for what you're doing.
So either change the col to a string and use the filter, or change the setter :)
Good luck!
The problem with doing it that way is that for a while, the non-normalized stuff will exist in the object; if you have code that works on the attributes before stuff gets normalised, then that will be a problem.
You could define a setter:
def thenumber=(value)
# normalise stuff here, call write_attribute
end
Unfortunately I think a lot of the Rails form stuff writes the attributes directly, which is one of the reasons I don't tend to use it.
Or you could normalise the params in the controller before you pass them through.
Does ruby let you interchange between a . and [''] ?
I don't know, I'll try later, but I think you are supposed to use .
self.thenumber = self.thenumber.to_s.gsub(',','')
You should return true from your before_validation method, otherwise if the expression being assigned to self['thenumber'] ends up being nil or false, the data will not be saved, per the Rails documention:
If a before_* callback returns false,
all the later callbacks and the
associated action are cancelled.
Ostensibly, you are trying to normalize here then check the result of the normalization with your Rails validations, which will decide if nil/false/blank are okay or not.
before_validation :normalize
def normalize
self['thenumber'] = self['thenumber'].to_s.gsub(',','')
return true
end