Elixir Ecto Changeset OR Validation - validation

I am attempting to perform a Changeset validation on either an email OR phone number, and I found a nifty OR changeset function from #Dogbert here here - but I cannot get my OR validation flow to work correctly.
Does anyone mind taking a quick look on why the email or phone validation is always returning a nil changeset?
#doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :phone])
|> validate_required_inclusion([:email, :phone])
|> validate_required_inclusion_format([:email, :phone])
end
defp validate_required_inclusion(changeset, fields) do
if Enum.any?(fields, &present?(changeset, &1)) do
changeset
else
# Add the error to the first field only since Ecto requires a field name for each error.
add_error(changeset, hd(fields), "One of these fields must be present: #{inspect fields}")
end
end
defp present?(changeset, field) do
value = get_field(changeset, field)
value && value != ""
end
## TODO - this doesnt work
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end
changeset
end
defp email_changeset(changeset) do
changeset
|> validate_required([:email])
|> validate_format(:email, ~r/.+#.+/)
|> unique_constraint(:email)
end
defp phone_changeset(changeset) do
changeset
|> validate_required([:phone])
|> valid_phone(:phone)
|> unique_constraint(:phone)
end
defp valid_phone(changeset, field) do
phone = get_field(changeset, field)
IO.inspect(phone, label: "phone: ")
{:ok, phone_number} = ExPhoneNumber.parse(phone, "US")
IO.inspect(phone_number, label: "ExPhoneNumber: ")
if ExPhoneNumber.is_valid_number?(phone_number) do
changeset
else
changeset
|> add_error(field, "Invalid Phone Number")
end
end
Thanks in advance!

You're not returning the modified changesets properly in validate_required_inclusion_format. In Elixir, the last value in a block is its return value. In if statements, the last value of both its true and false branch is its return value. If you don't have an else branch and the condition is false, the return value is nil.
Here's the simplest way to fix the problem: join the two top level if and the fallback changeset return with a ||:
defp validate_required_inclusion_format(changeset, fields) do
if Enum.member?(fields, :email) do
value = get_field(changeset, :email)
if value && value != "" do
IO.inspect(value, label: "email found: ")
changeset
|> email_changeset()
end
end || # <- note this
if Enum.member?(fields, :phone) do
value = get_field(changeset, :phone)
if value && value != "" do
IO.inspect(value, label: "phone found: ")
changeset
|> phone_changeset()
end
end || # <- and this
changeset
end
Now if the first or second if conditions are not met, you'll get a nil and the third if will be evaluated. If the third or fourth also not met, the final fallback changeset will be returned.
Note: the naming of this function is misleading. Unlike the function you used from my previous answer, you're not using fields at all here. You're better off not passing fields to this function and calling it something like add_email_or_phone_changeset, e.g.
if value = get_field(changeset, :email) do
...
end ||
if value = get_field(changeset, :phone) do
...
end || ...

Related

ruby - access array with hash key

I am struggling to understand how I can access an array with a hash key. In my code, I create a hash with keys and values. Now, I want to set the values in a Car class. Whenever I try to instantiate the Car, the argument expects Integer and not a String.
I am getting the following error: TypeError (no implicit conversion of String into Integer)
Here is my code:
class Car_maker
attr_accessor :car_maker
def initialize(car_maker)
#car_maker = car_maker
end
end
class Car_model < Car_maker
attr_accessor :km, :type, :transmission, :stock, :drivetrain, :status,
:fuel, :car_maker, :model, :year, :trim, :features
#total number of instances & array with car objects
##totalCars = 0
##catalogue = []
def initialize(km, type, transmission, stock, drivetrain, status, fuel, car_maker, model, year, trim, features)
super(car_maker)
#km = km
#type = type
#transmission = transmission
#stock = stock
#drivetrain = drivetrain
#status = status
#fuel = fuel
#model = model
#year = year
#trim = trim
#features = features
##totalCars += 1
end
def self.convertListings2Catalogue(line)
#Initialise arrays and use them to compare
type = ["Sedan", "coupe", "hatchback", "station", "SUV"]
transmission = ["auto", "manual", "steptronic"]
drivetrain = ["FWD", "RWD", "AWD"]
status = ["new", "used"]
car_maker = ["honda", "toyota", "mercedes", "bmw", "lexus"]
hash = Hash.new
#In this part, we hash the set of features using regex
copyOfLine = line
regex = Regexp.new(/{(.*?)}/)
match_array = copyOfLine.scan(regex)
match_array.each do |line|
hash["features"] = line
end
#Now, we split every comma and start matching fields
newStr = line[0...line.index('{')] + line[line.index('}')+1...line.length]
arrayOfElements = newStr.split(',')
arrayOfElements.each do |value|
if value.include?("km") and !value.include?("/")
hash["km"] = value
elsif type.include?(value)
hash["type"] = value
elsif transmission.include?(value.downcase)
hash["transmission"] = value
elsif value.include?("/") and value.include?("km")
hash["fuel economy"] = value
elsif drivetrain.include?(value)
hash["drivetrain"] = value
elsif status.include?(value.downcase)
hash["status"] = value
elsif /(?=.*[a-zA-Z])(?=.*[0-9])/.match(value) and !value.include?("km")
hash["stock"] = value
elsif car_maker.include?(value.downcase)
hash["carmaker"] = value
elsif /^\d{4}$/.match(value)
hash["year"] = value
elsif value.length == 2
hash["trim"] = value
else
if value.length > 2
hash["model"] = value
end
end
end
end
end
textFile = File.open('cars.txt', 'r')
textFile.each_line{|line|
if line.length > 2
result = Car_model.convertListings2Catalogue(line)
puts "Hash: #{result}"
carObj = Car_model.new(result["km"], result["type"], result["transmission"], result["stock"], result["drivetrain"],
result["status"], result["fuel"], result["carmaker"], result["model"], result["year"], result["trim"], result["features"])
###catalogue.push (carObj)
end
}
This line
result = Car_model.convertListings2Catalogue(line)
Doesn't return the hash object. It returns arrayOfElements since that's what the each method actually returns and the each method is the last method executed in the method (although there are hash assignments within it, it's only the last value that's returned unless you use an explicit return statement.
Just use the variable hash in the last line of the convertListing2Catalog method
if value.length > 2
hash["model"] = value
end
end
end
hash # < this is the last line of the method so it's the value that will be returned
end
end
If you think about it, there were several variables created in the method. There's no reason to expect that the contents of any specific variable such as hash would be returned, and ruby methods by default return the last executed command.

Why is this string value converted to an array?

I have the following function:
def parse_var(var)
value = instance_variable_get(var)
puts(value)
puts(value.to_s)
value.is_a?(Numeric) ? value.to_s : "\"#{value}\""
end
Variables of certain form are converted into an array when they are interpolated. In the above function, when value equals (684) 029-6183 x01024, value.to_s comes out to be ["(684) 029-6183 x01024", nil]. The same thing also happens when I try "#{value}".
What is causing this?
Here's the context of the code in question:
Entity.rb (context of above code)
require 'securerandom'
# Entity.rb
class Entity
def initialize
generate_uuid
end
def to_cypher
first_char = self.class.name.chr.downcase
"MERGE (#{first_char}:#{self.class.name} {#{attrs_to_cypher.join(', ')}}) RETURN #{first_char};"
end
protected
def rand_bool
[true, false].sample
end
private
def attrs_to_cypher
self.instance_variables.map do |var|
"#{camelize(var.to_s[1..-1])}:#{parse_var(var)}"
end
end
def generate_uuid
#uuid = SecureRandom.uuid
end
def parse_var(var)
value = instance_variable_get(var)
puts(value)
puts(value.to_s)
value.is_a?(Numeric) ? value.to_s : "\"#{value}\""
end
def camelize(s)
(s == "uuid") ? "UUID" : s.downcase.split('_').map(&:capitalize).join
end
end
PhoneNumber.rb (where the value is coming from)
require 'faker'
require_relative 'Entity'
# PhoneNumber.rb
class PhoneNumber < Entity
def initialize(**opts)
super()
#type = opts[:type] || rand_bool ? "cell" : "home"
#number = opts[:number] || #type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
#country_code = opts[:country_code] || nil
#area_code = opts[:area_code] || nil
end
end
The following line of code is causing a couple of issues
#number = opts[:number] || #type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
First, the || operator has a higher precedence than the ? operator, so it actually looks like:
#number = (opts[:number] || #type == "cell") ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number,
and you probably were wanting this:
#number = opts[:number] || (#type == "cell" ? Faker::PhoneNumber.cell_phone : Faker::PhoneNumber.phone_number),
As it stands, it doesn't matter what you pass into opts[:number], you'll always get a Faker::PhoneNumber assigned. (The line above, assigning type, looks like it would have this same precedence issue)
Second, you have a stray comma at the end of the line. This is turning the entire line into the first element of an array, and doesn't interfere with assigning the variable on the next line, so it's hard to catch:
opts = { number: '123' }
type = "cell"
number = opts[:number] || type == "cell" ? "truthy" : "falsey",
country = "some value"
p number # => ["truthy", "some value"]
p country # => "some value"
Your variable contains ["(684) 029-6183 x01024", nil], not a string. You rely on puts(value) output to determine it’s value which is plain wrong:
puts ["(684) 029-6183 x01024", nil]
#⇒ (684) 029-6183 x01024
#
puts(nil) returns an empty string, making you think the original value is a string.
To overcome this issue you might:
value = [*value].compact.join

what is the purpose of ".push" in this line "#errorLogin.push("Username should not be blank")" of code

Here is the code below:
I wanted to know the purpose of .push in this line: #errorLogin.push("Username should not be blank")
def loginValidations(errorLogin)
puts"inside loginValidations"
loginUsername = #params["username"]
puts "loginUsername: #{loginUsername}"
loginPass = #params["password"]
puts "loginPass: #{loginPass}"
logFlag=true
#count=0
if loginUsername == nil || loginUsername == ""
#errorLogin.push("Username should not be blank")
logFlag=false
end
#count=count+1
if loginPass == nil || loginPass == ""
#errorLogin.push("Password should not be blank")
logFlag=false
end
end
Array#push inserts an element at the end of the array.
a = [1,2,3]
a.push 4
# a = [1,2,3,4]
In that case probably that line inserts a failed validation in a variable that keeps track of login errors.

Redis client using Ruby

I am trying to get user input for AGE attribute and comparing it with my key value pair and trying to return those records which match the user input value.
Code:
require "redis"
require "csv"
require "json"
begin
redis = Redis.new(:url => "redis://h:petr08jsjnofis211gmbq8fdd8#ec2-54-83-9-36.compute-1.amazonaws.com:11959")
puts "Connected"
health = CSV.read('6339_Dataset_1.csv')
health.each do |data_row|
redis.hmset(data_row[21],"AGE",data_row[0],"SEX",data_row[1],"RACE",data_row[2],"DAY_OF_ADMISSION",data_row[3],"DISCHARGE_STATUS",data_row[4],"STAY_INDICATOR",data_row[5],"ID",data_row[21])
end
value = redis.keys('*')
puts "QUERY 2"
puts "Enter the AGE:"
age = gets
value.each do |data|
#puts id
val = redis.hgetall(data)
val.each do |key, value|
if key == "AGE" && value == age
puts redis.hgetall(data)
else
puts "NO"
end
end
end
end
Problem:
I am able to get the output if I hard coded AGE but if I get the user input it just goes to the else section.
Try using .to_i in the line you check for AGE key, like:
if key == "AGE" && value.to_i == age.to_i
Also, the second value variable shadows outer value variable.
Better to change value = redis.keys('*') to something like,
keys = redis.keys('*')

Ruby - return false unless a value is true in which case return the value

If a = false and b = 2 is there a concise way to accomplish this? Using just return a unless b returns 'nil' instead of '2'.
I have
def checkit
return a unless b
b
end
Will this statement call b twice?
A real life case for this is:
def current_user
#current_user ||= authenticate_user
end
def authenticate_user
head :unauthorized unless authenticate_from_cookie
end
def authenticate_from_cookie
.
if user && secure_compare(user.cookie, cookie)
return user
else
return nil
end
end
Try this:
( b == true ) ? a : false
where a is a value you need to return
I do not know why you have false stored in the variable a, so I omitted that. As I understand, you want to pass a value to the method checkit, which should return the value if its boolean value is true (which means everything except values nil and false), and otherwise return the value. In that case, just use this:
def checkit(value)
value || false
end
checkit(1) # => 1
checkit(false) # => false
checkit('value') # => "value"
checkit(nil) # => false

Resources