Conditionally add OR condition in query - ruby-on-rails

The orders table have a few columns, say email, tel and address.
User provides email/tel/address, and any of them can be nil or empty string (EDITED).
How to generate an OR query, so if any of the columns match, the record is returned?
The only catch is that if any value provided is nil or empty, that will be ignored instead.
I was able to do the following using Arel:
email = params[:email]
tel = params[:tel]
address = params[:address]
t = Order.arel_table
sq = t[:email].eq(email) if email.present?
sq = sq.or(t[:phone].eq(phone)) if phone.present?
sq = sq.or(t[:phone].eq(address)) if address.present?
Order.where( sq )
However it will err if email is nil, because sq will not instantiate.
I want to prevent constructing sql string, and I use Squeel gem.

you can put
Order.where("email=? or tel= ? or address=?", params[:email], params[:tel], params[:address])

You can check whether your params are nil or not by Ick's maybe. So read about Ick gem and follow the steps given there and then you can use it in your case like :
params[:email].maybe
params[:tel].maybe
params[:address].maybe
Hope, this is what you were looking for.

Please have a try with
where_condition = "true"
where_condition += "OR email = '#{params[:email]}'" if params[:email].present?
where_condition += "OR tel = '#{params[:tel]}'" if params[:tel].present?
where_condition += "OR address = '#{params[:address]}'" if params[:address].present?
Order.where(where_condition)

In order to prevent nil or empty string, I finally got the following:
t = Order.arel_table
conditions = [:email, :phone, :address].map{|attr|
attr_value = params[attr]
if attr_value.present?
t[attr].eq(attr_value)
else
nil
end
}.compact
if conditions.empty?
Order.where('1=0')
else
Order.where( conditions.inject{|c, cc| c.or(cc).expr} )
end
Ugly, but flexible.

Related

Rails: Post parameter is null

From a javascript file I am having post request with parameters:
Parameters: {"interview"=>{"participants"=>"1,2,3", "start_time"=>"2020-05-28T09:32:00.000Z", "end_time"=>"2020-05-28T10:32:00.000Z"}}
And i am accessing this in controller's create as:
puts("create entry")
#start_time = params[:start_time]
#end_time = params[:end_time]
p(#start_time)
p(#end_time)
participants = params[:participants].try(:split, ",")
p(participants)
But when check the values of p and puts are such as:
create entry
nil
nil
nil
I could not understand what is the reason behind this.
They're all "under" the interview key, so it should be:
#start_time = params[:interview][:start_time]
#end_time = params[:interview][:end_time]
participants = params[:interview][:participants].try(:split, ',')
Better in this case using dig:
participants = params.dig(:interview, :participants).try(:split, ',')
...
Or storing params[:interview] and giving a "default" value if nil:
interview = params[:interview] || {}

Assigning a ruby hash by map do where values don't consistantly exist

I have an map that I have to assemble before using...
mailList = Customers.map do |customer|
{
:username => customer.name,
:email => customer.email,
:last_ip_v4 => customer.ipv4,
:last_ip_v6 => customer.ipv6
}
end
This works for a number of test users, but not all users have a last ipv4 AND a last ipv6. When they don't, ruby errors out, but I'd rather it just assign a nil. How do I do that?
If you are sure that this line,
mailList = Customers.map do |customer|
will work fine (may be incase of using a wrong convention), then try to check the nil cases for ipv4 and ipv6 and construct the list as,
mailList = construct_list Customers
def construct_list data
list = []
hash ={}
data.map do |customer|
hash[:username] = customer.name
hash[:email] = customer.email
customer.ipv4.nil? ? hash[:last_ip_v4] = nil : hash[:last_ip_v4] = customer.ipv4
customer.ipv6.nil? ? hash[:last_ip_v6] = nil : hash[:last_ip_v6] = customer.ipv6
list << hash
end
list
end
Though it may not be optimal, it will help you for a while.
Even if ipv4 and ipv6 are nil, it should not throw error. I think the syntax here is incorrect or Customers is nil. Try this way:
mailList = Customer.all.map do |customer|
{
username: customer.name,
email: customer.email,
last_ip_v4: customer.ipv4,
last_ip_v6: customer.ipv6
}
end

Check whether a string contains numbers

I have input values like:
string = "devid"
string = "devid123"
string = "devid.123.devid"
I need to sort strings that contain .(number)., for example "devid.123.devid". How can I separate only strings that consist of .(numbers). like .123.? Help me find a solution.
In a controller, I have:
#person = Person.new
personname = params['personname']
if personname.match("/\d+/")
#person.person_name = personname
#person.save()
result = 'true'
end
When I execute this code, I get "devid123" and "devid.123.devid".
If its certain that the format of the valid personname is always
<string>.<number>.<string>
You can try using :[regex, index] method for strings in ruby.
https://ruby-doc.org/core-2.6.1/String.html#method-i-5B-5D
So if
personname = "devid.123.devid"
s[/(.*)(\.\d+\.)(.*)/, 2] = ".123."
There are three different groups in the regex (.*)(\.\d+\.)(.*).
Matches anything
Matches a .<number>.
Matches anything
So based on this regex, the second group should provide you .<number>. which, I hope, is what you need.
Tested with Ruby 2.4.1
If I understand this correctly you only want a string where the digits are preceded by .. If so you need to modify your regex to be /\.\d+/
#person = Person.new
personname=params['personname']
if personname.match("/\.\d+/")
#person.person_name = personname
#person.save
result = 'true'
end
But this sounds like logic you should be handling in the model, since this is tagged as rails and not plain old ruby
controller
class PersonController
def create
if #person = Person.create(params)
result = 'true'
else
result = 'false'
end
# whatever you doing with result
end
end
person.rb
class Person < ApplicationRecord
validates :personname, format: { with: /\.\d+\./, message: 'must include digits' }
end
You can play with the regex # rubular

Easier way to write If hash includes then - Ruby

I have the following in an initialize method on my model:
#home_phone = contact_hash.fetch('HomePhone')
However, sometimes I need this instead:
#home_phone = contact_hash.fetch('number')
Also, sometimes neither of those will be true and I will need the home_phone attribute to be empty.
How can I write this out without creating a big loop like so:
if contact_hash.has_key?('HomePhone')
#home_phone = contact_hash.fetch('HomePhone')
elsif contact_hash.has_key?('number')
#home_phone = contact_hash.fetch('number')
else
#home_phone = ""
end
You could try
#home_phone = contact_hash.fetch('HomePhone', contact_hash.fetch('number', ""))
or better
#home_phone = contact_hash['HomePhone'] || contact_hash['number'] || ""
contact_hash.values_at('HomePhone','number','home_phone').compact.first
Edit:
My first solution did not really give the answer asked for. Here is a modified version, although I think in the case of only 3 options the solution given by #knut is better.
contact_hash.values_at('HomePhone','number').push('').compact.first
def doit(h, *args)
args.each {|a| return h[a] if h[a]}
""
end
contact_hash = {'Almost HomePhone'=>1, 'number'=>7}
doit(contact_hash, 'HomePhone', 'number') # => 7
You could use values_at I suppose:
#home_phone = contact_hash.values_at('HomePhone', 'number').find(&:present?).to_s
That isn't exactly shorter but it wouldn't be convenient if you had the keys in an array:
try_these = %w[HomePhone number]
#home_phone = contact_hash.values_at(*try_these).find(&:present?).to_s
You could also wrap that up in a utility method somewhere or patch it into Hash.

Get multiple records with one query

User table:
name lastname
Bob Presley
Jamie Cox
Lucy Bush
Roman Cox
Find users
q = Query.new("Bob Presley, Cox, Lucy")
q.find_users => {0=>{:name=>"Bob", :lastname=>"Presley"}, 1=>{:lastname=>"Cox"}, 2=>{:name=>"Lucy"}}
Question:
I've got hash with few names and lastnames. I need to build Activerecord query to fetch all users from that hash.
If i have name and lastname I should find user with exactly the same name and lastname.
If I have only lastname or name I should find all users with this name or lastname. So when i search for :lastname => Cox it should return two users [Roman Cox,Jamie Cox]
I can do
object = []
hash = q.find_users
hash.each do |data|
#Pseudocode
# object << User.where(:name => if data[:lastname] exist, :lastname => if data[:name] exist)
end
But I think it is higly inefficient. How should I do this ?
Environment
rails: 3.0.3
ruby: 1.9.2-head
gem: meta_search https://github.com/ernie/meta_search
I'm sure this can be refactored nicely (hint!), but this code below will construct a SQL which can be used in a sub-select.
Code below does not sanitize the input values.
Note that you should sanitize the values in the h hash!
h = {0=>{:name=>"Bob", :lastname=>"Presley"}, 1=>{:lastname=>"Cox"}, 2=>{:name=>"Lucy"}}
conditions = ""
h.each_pair do |k,v|
if not conditions.empty?
conditions += " or "
end
conditions += "("
a_condition = ""
v.each_pair do |a,b|
if not a_condition.empty?
a_condition += " and "
end
a_condition += "#{a.to_s} = '#{b}'"
end
conditions += a_condition
conditions += ")"
end
conditions = "("+conditions+")"
p conditions
# => "((name = 'Bob' and lastname = 'Presley') or (lastname = 'Cox') or (name = 'Lucy'))"
# use the generated SQL conditions to find the users
#users = User.find(:all, :conditions => "(#{conditions})")

Resources