How to apply multiple regular expression on single field - ruby-on-rails

Currently I have a regular expression for zip-codes for the U.S.:
validates :zip,
presence: true,
format: { with: /\A\d{5}(-\d{4})?\z/ }
I want to use different regular expressions for other countries on the same zip-code so the regular expression should be used according to the country:
For Australia 4 digits
For Canada 6 digits alphanumeric
For UK 6-7 digits alphanumeric
Can someone suggest how can I full fill my requirement?

You can give a lambda that returns a Regexp as the :with option for the format validator (see :with), which makes this nice and clean:
ZIP_COUNTRY_FORMATS = {
'US' => /\A\d{5}(-\d{4})?\z/,
'Australia' => /\A\d{4}\z/,
# ...
}
validates :zip, presence: true,
format: { with: ->(record){ ZIP_COUNTRY_FORMATS.fetch(record.country) } }
Note that uses Hash#fetch instead of Hash#[] so that if a country that doesn't exist is given it will raise a KeyError just as a sanity check. Alternatively you could return a default Regexp that matches anything:
ZIP_COUNTRY_FORMATS.fetch(record.country, //)
...or nothing:
ZIP_COUNTRY_FORMATS.fetch(record.country, /.\A/)
...depending on the behavior you want.

You would want to write a method to help you:
validates :zip, presence: true, with: :zip_validator
def zip_validator
case country
when 'AU'
# some regex or fail
when 'CA'
# some other regex or fail
when 'UK'
# some other regex or fail
else
# should this fail?
end
end

Suppose we give examples of valid postal codes for each country in a hash such as the following.
example_pcs = {
US: ["", "98230", "98230-1346"],
CAN: ["*", "V8V 3A2"],
OZ: ["!*", "NSW 1130", "ACT 0255", "VIC 3794", "QLD 4000", "SA 5664",
"WA 6500", "TAS 7430", "NT 0874"]
}
where the first element of each array is a string of codes that will be explained later.
We can construct a regex for each country from this information. (The information would undoubtedly be different in a real application, but I am just presenting the general idea.) For each country we construct a regex for each example postal code, using in part the above-mentioned codes. We then take the union of those regexes to obtain a single regex for that country. Here's one way the regex for an example postal code might be constructed.
def make_regex(str, codes='')
rstr = str.each_char.chunk do |c|
case c
when /\d/ then :DIGIT
when /[[:alpha:]]/ then :ALPHA
when /\s/ then :WHITE
else :OTHER
end
end.
map do |type, arr|
case type
when :ALPHA
if codes.include?('!')
arr
elsif arr.size == 1
"[[:alpha:]]"
else "[[:alpha:]]\{#{arr.size}\}"
end
when :DIGIT
(arr.size == 1) ? "\\d" : "\\d\{#{arr.size}\}"
when :WHITE
case codes
when /\*/ then "\\s*"
when /\+/ then "\\s+"
else (arr.size == 1) ? "\\s" : "\\s\{#{arr.size}\}"
end
when :OTHER
arr
end
end.
join
Regexp.new("\\A" << rstr << "\\z")
end
I've made the regex case-insensitive for letters, but that could of course be changed. Also, for some countries, the regex produced may have to be tweaked manually and/or some pre- or post-processing of postal code strings may be called for. For example, some combinations may have the correct format but nonetheless are not valid postal codes. In Australia, for example, the four digits following each region code must fall within specified ranges that vary by region.
Here are some examples.
make_regex("12345")
#=> /\A\d{5}\z/
make_regex("12345-1234")
#=> /\A\d{5}-\d{4}\z/
Regexp.union(make_regex("12345"), make_regex("12345-1234"))
#=> /(?-mix:\A\d{5}\z)|(?-mix:\A\d{5}-\d{4}\z)/
make_regex("V8V 3A2", "*")
#=> /\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/
make_regex("NSW 1130", "!*")
# => /\ANSW\s*\d{4}\z/
Then, for each country, we take the union of the regexes for each example postal code, saving those results as values in a hash whose keys are country codes.
h = example_pcs.each_with_object({}) { |(country, (codes, *examples)), h|
h[country] = Regexp.union(examples.map { |s| make_regex(s, codes) }.uniq) }
#=> {:US=>/(?-mix:\A\d{5}\z)|(?-mix:\A\d{5}-\d{4}\z)/,
# :CAN=>/\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/,
# :OZ=>/(?-mix:\ANSW\s*\d{4}\z)|(?-mix:\AACT\s*\d{4}\z)|(?-mix:\AVIC\s*\d{4}\z)|(?-mix:\AQLD\s*\d{4}\z)|(?-mix:\ASA\s*\d{4}\z)|(?-mix:\AWA\s*\d{4}\z)|(?-mix:\ATAS\s*\d{4}\z)|(?-mix:\ANT\s*\d{4}\z)/}
"12345" =~ h[:US]
#=> 0
"12345-1234" =~ h[:US]
#=> 0
"1234" =~ h[:US]
#=> nil
"12345 1234" =~ h[:US]
#=> nil
"V8V 3A2" =~ h[:CAN]
#=> 0
"V8V 3A2" =~ h[:CAN]
#=> 0
"V8v3a2" =~ h[:CAN]
#=> 0
"3A2 V8V" =~ h[:CAN]
#=> nil
"NSW 1132" =~ h[:OZ]
#=> 0
"NSW 1132" =~ h[:OZ]
#=> 0
"NSW1132" =~ h[:OZ]
#=> 0
"NSW113" =~ h[:OZ]
#=> nil
"QLD" =~ h[:OZ]
#=> nil
"CAT 1132" =~ h[:OZ]
#=> nil
The steps performed in make_regex for
str = "V8V 3A2"
codes = "*+"
are as follows.
e = str.each_char.chunk do |c|
case c
when /\d/ then :DIGIT
when /[[:alpha:]]/ then :ALPHA
when /\s/ then :WHITE
else :OTHER
end
end
#=> #<Enumerator: #<Enumerator::Generator:0x007f9ff201a330>:each>
We can see the values that will be generated by this enumerator by converting it to an array.
e.to_a
#=> [[:ALPHA, ["V"]], [:DIGIT, ["8"]], [:ALPHA, ["V"]], [:WHITE, [" "]],
# [:DIGIT, ["3"]], [:ALPHA, ["A"]], [:DIGIT, ["2"]]]
Continuing,
a = e.map do |type, arr|
case type
when :ALPHA
if codes.include?('!')
arr
elsif arr.size == 1
"[[:alpha:]]"
else "[[:alpha:]]\{#{arr.size}\}"
end
when :DIGIT
(arr.size == 1) ? "\\d" : "\\d\{#{arr.size}\}"
when :WHITE
case codes
when /\*/ then "\\s*"
when /\+/ then "\\s+"
else (arr.size == 1) ? "\\s" : "\\s\{#{arr.size}\}"
end
when :OTHER
arr
end
end
#=> ["[[:alpha:]]", "\\d", "[[:alpha:]]", "\\s*", "\\d", "[[:alpha:]]", "\\d"]
rstr = a.join
#=> "[[:alpha:]]\\d[[:alpha:]]\\s*\\d[[:alpha:]]\\d"
t = "\\A" << rstr << "\\z"
#=> "\\A[[:alpha:]]\\d[[:alpha:]]\\s*\\d[[:alpha:]]\\d\\z"
puts t
#=> \A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z
Regexp.new(t)
#=> /\A[[:alpha:]]\d[[:alpha:]]\s*\d[[:alpha:]]\d\z/

Related

How to check if each word in an array contains a substring and reject those in Ruby on Rails?

a = ["SUPER", "SOME_VALID", "ROME_INVALID", "SUPER_GOOD"]
a = a.reject { |x| x.in? ["GOOD", "VALID"]}
#=> ["SUPER", "SOME_VALID", "ROME_INVALID", "SUPER_GOOD"]
I don't want words that contain substring VALID or GOOD.
Output should be ["SUPER"] only.
grep_v would work:
a = ["SUPER", "SOME_VALID", "ROME_INVALID", "SUPER_GOOD"]
a = a.grep_v(/GOOD|VALID/)
#=> ["SUPER"]
You could say this:
a = a.reject { |x| x.include?("GOOD") || x.include?("VALID")}
What in? does is to check if the receiver is present in the array passed as argument, meaning:
1.in?([1, 2]) # true
3.in?([1, 2]) # false
That's to say, it checks for the "whole" object, rather than a part of it.
If you want to reject the elements in your array that match with VALID and/or GOOD, you can use =~:
["SUPER", "SOME_VALID", "ROME_INVALID", "SUPER_GOOD"].reject { |word| word =~ /VALID|GOOD/ } # ["SUPER"]
Notice, this is also going to reject words like "VALIDITY", "GOODNESS", etc.
You could use include?:
a.reject { |x| ["GOOD", "VALID"].any?{ |word| x.include?(word) } }
#=> ["SUPER"]

How to return the hash key if the value is true?

Given a hash
PLATFORMS = {
:mac => /(mac)|(macintosh)/i,
:win => /(win)|(windows)/i,
:ipad => /(ipad)/i,
:iphone => /(iphone)/i,
:ipod => /(ipod)|(ipod touch)/i
}
I am interested in returning the hash key, where the value (in this case is a regex expression) returns true.
So if I am given a string of "windows", I should be returned the key :win.
My attempt so far is:
current_platform = BrowserExperience::ExperienceKeeper::PLATFORMS.detect do |platform, regex|
regex.match(user_agent_obj.platform)
end[0]
It returns [:win,/(win)|(windows)/i]
However, that just returns an array, where index 0 returns the key value I desire. Is there a simpler way?
Why not use a case statement? It's a more common way to do this:
strings = [
'this is a Windows box',
'Welcome to Macintosh',
'My music is on an iPod',
'My photos are on an iPod Touch',
'I read books on an iPad'
]
strings.each do |str|
os = case str
when /\b(?:mac|macintosh)\b/i
:mac
when /\b(?:win|windows)\b/i
:win
when /\b(?:ipad)\b/i
:ipad
when /\b(?:iphone)\b/i
:iphone
when /\b(?:ipod|ipod\ touch)\b/i
:ipod
end
os # => :win, :mac, :ipod, :ipod, :ipad
end
It's also possible to do something like this:
PLATFORMS = {
mac: /\b(?:mac|macintosh)\b/i,
win: /\b(?:win|windows)\b/i,
ipad: /\b(?:ipad)\b/i,
iphone: /\b(?:iphone)\b/i,
ipod: /\b(?:ipod|ipod\ touch)\b/i
}
strings.each do |str|
key = nil
PLATFORMS.each_pair do |k, v|
if str =~ v
key = k
break
end
end
key # => :win, :mac, :ipod, :ipod, :ipad
end
Or best:
strings.each do |str|
PLATFORMS.find { |k, v| str =~ v }.first # => :win, :mac, :ipod, :ipod, :ipad
end
If you go with a hash and regular expressions, be more concise with your patterns. \b is a word boundary, and is how we tell the Regexp engine whether to match on substrings or whole words:
'machine'[/(?:mac|macintosh)/i] # => "mac"
vs:
'machine'[/\b(?:mac|macintosh)\b/i] # => nil
Here's a bit more:
'mac'[/\b(?:mac|macintosh)\b/i] # => "mac"
'macintosh'[/\b(?:mac|macintosh)\b/i] # => "macintosh"
'win'[/\b(?:win|windows)\b/i] # => "win"
'windows'[/\b(?:win|windows)\b/i] # => "windows"
'ipad'[/\b(?:ipad)\b/i] # => "ipad"
'iphone'[/\b(?:iphone)\b/i] # => "iphone"
'ipod touch'[/\b(?:ipod|ipod\ touch)\b/i] # => "ipod"
I'd probably do something like this to define the hash:
require 'regexp_trie'
PLATFORMS = {
mac: ['mac', 'macintosh'],
win: ['win', 'windows'],
ipad: ['ipad'],
iphone: ['iphone'],
ipod: ['ipod', 'ipod touch']
}
Then, I'd transform the patterns to something more efficient:
PLATFORMS_RE = {}
PLATFORMS.each_pair do |k, v|
PLATFORMS_RE[k] = /\b(?:#{RegexpTrie.union(v).source})\b/i
end
which results in:
PLATFORMS_RE
# => {:mac=>/\b(?:mac(?:intosh)?)\b/i,
# :win=>/\b(?:win(?:dows)?)\b/i,
# :ipad=>/\b(?:ipad)\b/i,
# :iphone=>/\b(?:iphone)\b/i,
# :ipod=>/\b(?:ipod(?:\ touch)?)\b/i}
Which then works as before:
strings.each do |str|
PLATFORMS_RE.find { |k, v| str =~ v }.first # => :win, :mac, :ipod, :ipod, :ipad
end
Not a big improvement, but you could detect just keys instead of key-value pairs
platform_to_regex = BrowserExperience::ExperienceKeeper::PLATFORMS
current_platform = platform_to_regex.keys.detect do |platform|
platform_to_regex[platform].match(user_agent_obj.platform)
end
For the given hash I would write the following.
def attempt_match(str)
PLATFORMS.keys.find { |k| str.match? PLATFORMS[k] }
end
attempt_match 'windows' #=> :win
attempt_match 'DOS' #=> nil
A DRYer approach would be to use a simpler hash and construct the regular expressions (which are all quite similar) dynamically. This could be implemented as follows1.
def attempt_match(h, s)
sdn = s.downcase
h.find { |k, _| sdn.match?(/\b#{k}\b/) }&.last
end
h = {"mac"=>:mac, "windows"=>:win, "ipad"=>:ipad, "iphone"=>:iphone, "ipod"=>:ipod}
attempt_match(h, 'windows') #=> :win
attempt_match(h, 'DOD') #=> :nil
& in &.last is Ruby's safe navigation operator, introduced in v2.3.
It would be a simple matter to modify the hash (add "linux"=>:linux, for example) without having to worry about getting the regex right.
1 Observe that if "ipod touch" were matched, so would "ipod". Therefore, the former is redundant.
You could use select and to check using match which (or which ones) key value match with the string being passed:
PLATFORMS = {
mac: /(mac)|(macintosh)/i,
win: /(win)|(windows)/i,
ipad: /(ipad)/i,
iphone: /(iphone)/i,
ipod: /(ipod)|(ipod touch)/i
}
def match_regex(string)
PLATFORMS.select{|_,v| string.match(v)}.keys[0]
end
p match_regex('windows')
# => :win

Ordering a rails model with string outline notation

I have a rails model that the primary field that the user wants to sort on is a Line Item that is stored in dot-notation format as a string (i.e.: 2.1.4, 2.1.4.1, 2.1.4.5, etc). Ordering alphabetically works great, except that 2.1.4.10 comes before 2.1.4.2 alphabetically. What I want to call 'dot-based numeric order' would put 2.1.4.10 after 2.1.4.9, and 2.4.1.10.1 would precede 2.4.1.11
The question is this: What is The Rails Way™ to set the default order on the model so that the Line Items appear in the correct order, according to 'dot-based numeric order'.
Presume a simple case:
class LineItem < ActiveRecord::Base
validates :line_item, :presence => true, :uniqueness => true
end
and that :line_item is a string.
I assume you are using PostgreSQL and if you really want to set default order for your model, add this default_scope to your LineItem model:
default_scope -> { order("STRING_TO_ARRAY(line_item, '.')::int[] ASC") }
Otherwise I suggest you to use named scope, it can be override and chained:
scope :version_order, -> { order("STRING_TO_ARRAY(line_item, '.')::int[] ASC") }
To do this yourself:
lines = ['3.3.3.3', '3.54.3.3', '3.3.3.20']
sorted = lines.sort do |a, b|
a.split('.').zip(b.split('.')).inject(0) do |res, val|
(res == 0)? val[0].to_i <=> val[1].to_i : res
end
end #=> ["3.3.3.3", "3.3.3.20", "3.54.3.3"]
How it works:
To sort, we pass an array and a block, that blocks gives us 2 arguments that are next to each other in the list, and we can return 0, -1, or 1, which tells Ruby which directions to swap the numbers.
[4,3,-1,2].sort do |x, y|
if x > y
1
elsif x < y
-1
else
0
end
end #=> [-1, 2, 3, 4]
Instead of doing that long logic, Ruby provides a nice operator for us: <=>. Zero means no change, -1 means it's in ascending order, and 1 means the two numbers are in descending order. Ruby repeats that a bunch, and sorts the list.
4 <=> 4 #=> 0
3 <=> 5 #=> -1
5 <=> 3 #=> 1
7 <=> -1 #-> 1
So, we should give higher items (in terms of dots) priority:
#Pseudo Code:
33.44 > 22.55 #=> true
33.44 < 44.33
The easiest way to integrate through all the numbers is an #inject, which gives you a value, and the item you are on. You can do things like this:
[4,4,4].inject(0) {|sum, i| sum + i} #=> 12
[4,4,4].inject(0) {|sum, i| sum - i} #=> -12
['Hello', "I'm penne12"] {|new_word, word| new_word + "-" + word} #=> "Hello-I'm penne12"
So, we'll use an inline if:
(true)? "it's true" : "true is now false. Yay!" #=> "it's true"
(4 > 5)? "logic is weird" : "4 > 5" #=> "4 > 5"
Like this:
.inject(0) do |res, val|
(res == 0)? val[0].to_i <=> val[1].to_i : res
end
We'll split both strings by the ., to get a list:
"Hello. This. Is. A. Test.".split('.') #=> ["Hello", " This", " Is", " A", "Test"]
"4.4.4.4" #=> [4,4,4,4]
And join the two lists together by element using ruby's #Zip (it's really weird.)
[4,4,4,4].zip([5,5,5,5]) #=> [[4,5], [4,5], [4,5], [4,5]]
You can change what item a and b are, if you want to sort by a different property. Ruby doesn't care what you do to either variable, it only cares about the return value.
a, b = a.line_item, b.line_item
On a model:
class LineItem < ActiveRecord::Base
validates :line_item, :presence => true, :uniqueness => true
def self.sort_by_dbno
self.all.sort do |a, b|
a, b = a.line_item, b.line_item
a.split('.').zip(b.split('.')).inject(0) do |res, val|
(res == 0)? val[0].to_i <=> val[1].to_i : res
end
end
end
end
I overrode the <=> operator with #Penne12's code:
def <=>(y)
self.line_item.split('.').zip(y.line_item.split('.')).inject(0) do |res, val|
(res == 0)? val[0].to_i <=> val[1].to_i : res
end
end
Sorting works on any enumerable collection, with no sort block:
bobs_items = LineItem.where(:owner => bob, :complete => false)
"Bob's workload: #{bobs_items.sort.map { |li| li.line_item }.join(', ')}"

Rails Search ActiveRecord with Logical Operators

I'm wondering what the best way to parse a text query in Rails is, to allow the user to include logical operators?
I'd like the user to be able to enter either of these, or some equivalent:
# searching partial text in emails, just for example
# query A
"jon AND gmail" #=> ["jonsmith#gmail.com"]
# query B
"jon OR gmail" #=> ["jonsmith#gmail.com", "sarahcalaway#gmail.com"]
# query C
"jon AND gmail AND smith" #=> ["jonsmith#gmail.com"]
Ideally, we could get even more complex with parentheses to indicate order of operations, but that's not a requirement.
Is there a gem or a pattern that supports this?
This is a possible but inefficient way to do this:
user_input = "jon myers AND gmail AND smith OR goldberg OR MOORE"
terms = user_input.split(/(.+?)((?: and | or ))/i).reject(&:empty?)
# => ["jon myers", " AND ", "gmail", " AND ", "smith", " OR ", "goldberg", " OR ", "MOORE"]
pairs = terms.each_slice(2).map { |text, op| ["column LIKE ? #{op} ", "%#{text}%"] }
# => [["column LIKE ? AND ", "%jon myers%"], ["column LIKE ? AND ", "%gmail%"], ["column LIKE ? OR ", "%smith%"], ["column LIKE ? OR ", "%goldberg%"], ["column LIKE ? ", "%MOORE%"]]
query = pairs.reduce([""]) { |acc, terms| acc[0] += terms[0]; acc << terms[1] }
# => ["column LIKE ? AND column LIKE ? AND column LIKE ? OR column LIKE ? OR column LIKE ? ", "%jon myers%", "%gmail%", "%smith%", "%goldberg%", "%MOORE%"]
Model.where(query[0], *query[1..-1]).to_sql
# => SELECT "courses".* FROM "courses" WHERE (column LIKE '%jon myers%' AND column LIKE '%gmail%' AND column LIKE '%smith%' OR column LIKE '%goldberg%' OR column LIKE '%MOORE%' )
However, as I said, searches like this one are extremely inefficient. I'd recommend you use a full-text search engine, like Elasticsearch.
I use such a parser in a Sinatra app, since the queries tend to be complex I produce plain SQL instead of using the activerecords selection methods.
If you can use it, feel free..
You use it like this, class_name is the activerecord class representing the table, params is a hash of strings to parse, the result is sent to the browser as Json
eg
generic_data_getter (Person, {age: ">30",name: "=John", date: ">=1/1/2014 <1/1/2015"})
def generic_data_getter (class_name, params, start=0, limit=300, sort='id', dir='ASC')
selection = build_selection(class_name, params)
data = class_name.where(selection).offset(start).limit(limit).order("#{sort} #{dir}")
{:success => true, :totalCount => data.except(:offset, :limit, :order).count, :result => data.as_json}
end
def build_selection class_name, params
field_names = class_name.column_names
selection = []
params.each do |k,v|
if field_names.include? k
type_of_field = class_name.columns_hash[k].type.to_s
case
when (['leeg','empty','nil','null'].include? v.downcase) then selection << "#{k} is null"
when (['niet leeg','not empty','!nil','not null'].include? v.downcase) then selection << "#{k} is not null"
when type_of_field == 'string' then
selection << string_selector(k, v)
when type_of_field == 'integer' then
selection << integer_selector(k, v)
when type_of_field == 'date' then
selection << date_selector(k, v)
end
end
end
selection.join(' and ')
end
def string_selector(k, v)
case
when v[/\|/]
v.scan(/([^\|]+)(\|)([^\|]+)/).map {|p| "lower(#{k}) LIKE '%#{p.first.downcase}%' or lower(#{k}) LIKE '%#{p.last.downcase}%'"}
when v[/[<>=]/]
v.scan(/(<=?|>=?|=)([^<>=]+)/).map { |part| "#{k} #{part.first} '#{part.last.strip}'"}
else
"lower(#{k}) LIKE '%#{v.downcase}%'"
end
end
def integer_selector(k, v)
case
when v[/\||,/]
v.scan(/([^\|]+)([\|,])([^\|]+)/).map {|p|p p; "#{k} IN (#{p.first}, #{p.last})"}
when v[/\-/]
v.scan(/([^-]+)([\-])([^-]+)/).map {|p|p p; "#{k} BETWEEN #{p.first} and #{p.last}"}
when v[/[<>=]/]
v.scan(/(<=?|>=?|=)([^<>=]+)/).map { |part| p part; "#{k} #{part.first} #{part.last}"}
else
"#{k} = #{v}"
end
end
def date_selector(k, v)
eurodate = /^(\d{1,2})[-\/](\d{1,2})[-\/](\d{1,4})$/
case
when v[/\|/]
v.scan(/([^\|]+)([\|])([^\|]+)/).map {|p|p p; "#{k} IN (DATE('#{p.first.gsub(eurodate,'\3-\2-\1')}'), DATE('#{p.last.gsub(eurodate,'\3-\2-\1')}'))"}
when v[/\-/]
v.scan(/([^-]+)([\-])([^-]+)/).map {|p|p p; "#{k} BETWEEN DATE('#{p.first.gsub(eurodate,'\3-\2-\1')}')' and DATE('#{p.last.gsub(eurodate,'\3-\2-\1')}')"}
when v[/<|>|=/]
parts = v.scan(/(<=?|>=?|=)(\d{1,2}[\/-]\d{1,2}[\/-]\d{2,4})/)
selection = parts.map do |part|
operator = part.first ||= "="
date = Date.parse(part.last.gsub(eurodate,'\3-\2-\1'))
"#{k} #{operator} DATE('#{date}')"
end
when v[/^(\d{1,2})[-\/](\d{1,4})$/]
"#{k} >= DATE('#{$2}-#{$1}-01') and #{k} <= DATE('#{$2}-#{$1}-31')"
else
date = Date.parse(v.gsub(eurodate,'\3-\2-\1'))
"#{k} = DATE('#{date}')"
end
end
The simplest case would be extract an array from the strings:
and_array = "jon AND gmail".split("AND").map{|e| e.strip}
# ["jon", "gmail"]
or_array = "jon OR sarah".split("OR").map{|e| e.strip}
# ["jon", "sarah"]
Then you could construct an query string:
query_string = ""
and_array.each {|e| query_string += "%e%"}
# "%jon%%gmail%"
Then you use a ilike or a like query to fetch the results:
Model.where("column ILIKE ?", query_string)
# SELECT * FROM model WHERE column ILIKE '%jon%%gmail%'
# Results: jonsmith#gmail.com
Of course that could be a little overkill. But it is a simple solution.

Bringing values into a loop in Ruby

This is almost certainly a duplicate, but I can't find the original - I don't know the search terms to use. Which is why I'm on Stackoverflow instead of Google :)
Anyhow, here's my code:
def titleize(say)
index = 0
words = say.split
words.each do |word|
unless word == "and" || "or" || "over" || "the" || "for"
word.capitalize!
end
if index == 0
word.capitalize!
end
index += 1
end
say = words.join(" ")
end
Because index is declared before the loop, my if index == 0 is not working.
How do I let Ruby know about and use my object index? Also: what is this called?
Using index == 0 is perfectly fine as index is accessible within your loop. Your real problem is probably in this line:
word == "and" || "or" || "over" || "the" || "for"
This is always true-like! What you mean is:
["and", "or", "over", "the", "for"].include? word
Apart form that there is a method called each_with_index, which you can use like this:
words.each_with_index do |word, index|
I think you want to use with_index. Your word comparison was busted too.
def titleize(say)
words = say.split
l = ["and", "or", "over", "the", "for"]
words.each.with_index do |word, index|
word.capitalize! if index == 0 || !(l.include? word)
end
say = words.join(" ")
end
puts(titleize("hello there for you"))
puts(titleize("hi"))
puts(titleize("for"))
That's not how booleans work. The way this is evaluated is:
x == 'a' || 'b'
Becomes:
(x == 'a') || 'b'
Which is equivalent to:
'b'
What you're intending, translated to more idiomatic Ruby, is:
def titleize(say)
say.split.each_with_index do |word, index|
if (index == 0)
word.capitalize!
else
case (word)
when "a", "and", "or", "over", "the", "for"
# Leave lower-case
else
word.capitalize!
end
end
end.join(' ')
end
titleize('the time this is a test for the things!')
# => "The Time This Is a Test for the Things!"
I would do this it is more flexible and more ruby-esque
def titleize(sentence,exclusions=[])
sentence.split.map.with_index do |word,index|
(index == 0 || !exclusions.include?(word)) ? word.capitalize : word
end.join(' ')
end
For this case i used 'capitalize' without the bang in case any of the words are already capitalized.
"Hello".capitalize! #=> nil
"Hello".capitalize #=> "Hello"
It will also let you re-use the same list of exclusion or change them as you see fit
Call as
exclude = ["and", "or", "over", "the", "for"]
titleize("hello there you are over there", exclude)
#=> "Hello There You Are over There"
Your code returns the modification of say, but does change the contents of the variable. It appears that you want to modify the argument, but I'm not sure about that. I will first suggest a way to return the modified value of say (but not alter the value of say, and then will show how you could change the code to modify the argument.
Notice that I do not employ an index, and use a case statement to determine whether words after the first should be capitalized.
Code
def titleize(say)
words = say.split
return "" if words.empty?
words.first.capitalize!
return words.first if words.size == 1
words[1..-1].each do |word|
case word
when "and", "or", "over", "the", "for"
else
word.capitalize!
end
end
words.join(' ')
end
Examples
say = "and now is the time for all Rubyists to hunker down and code"
titleize(say)
#=> "And Now Is the Time for All Rubyists To Hunker Down and Code"
say
#=> "and now is the time for all Rubyists to hunker down and code"
say = " "
titleize(say)
#=> ""
say = " and "
titleize(say)
#=> "And"
Modifying the Argument
If you wish to modify the argument say, use String#replace:
def titleize_and_modify_arg(say)
words = say.split
str =
case words.size
when 0
""
when 1
words.first.capitalize
else
words.first.capitalize!
words[1..-1].each do |word|
case word
when "and", "or", "over", "the", "for"
else
word.capitalize!
end
end
words.join(' ')
end
say.replace(str)
end
say = "and now is the time for all Rubyists to hunker down and code"
titleize_and_modify_arg(say)
#=> "And Now Is the Time for All Rubyists To Hunker Down and Code"
say
#=> "And Now Is the Time for All Rubyists To Hunker Down and Code"
say = " and "
titleize_and_modify_arg(say)
#=> nil
say
#=> " and "
Notice that in the second example, titleize_and_modify_arg modifies say correctly, but returns nil. Of course, the method could be easily changed to return the value of say, as well as changing it, if that were desired.
Note also that, in the case statement, when words.siz => 1, it's capitalize, not capitalize!, as the latter would return nil if the word is already capitalized. capitalize! is need for the else case, however.
I recommend using each_index instead of each. See here.
Try this:
def titleize (say)
words = say.split
words.each_index do |index|
word = words[i]
unless word == "and" || "or" || "over" || "the" || "for"
word.capitalize!
end
if index == 0
word.capitalize!
end
end
say = words.join(" ")
end

Resources