forward email with attachments - ruby

I would like to do this without downloading the attachments and then re/attaching to the new email.
This is what i have tried:
$emailslist.each do |e|
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
subject "[Events] #{subjectstr}"
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
end
end
#error in 'e.attachments.each'=>undefined method `attachments' for
#<TypeError: can't convert nil into String>
EDIT
I have been using this code for months and it worked fine.
The new stuff i have introduced now is the code above.
Anyways I'm pasting the whole code upon request.
require 'mail'
$subscribers=[]
File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/subscribers2.txt",'r').each do |line|
line=line.sub("\n","")
$subscribers.push(line) if line =~ /#/
end
puts $subscribers
$errorfile=File.new("C:/Users/j.de_miguel/Desktop/mailman.forma/error_log2.txt",'a+')
$errorfile.write("#{Time.now}\n")
$errorfile.flush
def deleteSubjectRecursion(subjstr)
if subjstr =~ /(.\[FORMA 2013\])+/
subjstr.gsub!(/.\[FORMA 2013\]/,"")
end
if subjstr =~ /((?i)Re: ){2,}/
subjstr.gsub!(/((?i)Re: ){2,}/,"Re: ")
end
return subjstr
end
def UserIsRegistered(mailaddr)
registered = false
$subscribers.each{|s| registered = true if mailaddr==s}
if registered == false
$errorfile.write("#{Time.now} : user #{mailaddr} attempted to mailman\n")
$errorfile.flush
end
return registered
end
Mail.defaults do
retriever_method :imap, { :address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false }
delivery_method :smtp, { :address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true }
end
#$emailslist=Mail.find(keys: ['NOT','SEEN'])
$emailslist=[Mail.last]
$emailslist.each do |e|
eplain_part = e.text_part ? e.text_part.body.decoded : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type=e.charset
type_plain=eplain_part ? e.text_part.charset.to_s : nil
type_html=ehtml_part ? e.html_part.charset.to_s : nil
bodystr= type ? e.body.decoded.to_s.force_encoding(type) : nil
type=type ? type.to_s : type_plain
puts type.inspect
subjectstr=e.subject.to_s.encode(type)
fromstr=e.from.first.to_s.encode(type)
puts fromstr
bodystr_plain=eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html=ehtml_part ? ehtml_part.force_encoding(type_html) : nil
$subscribers.each do |tostr|
puts tostr.inspect
if (not subjectstr =~ /^\[FORMA 2013\]/ ) && (UserIsRegistered(fromstr) == true)
subjectstr=deleteSubjectRecursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc tostr
subject "[FORMA 2013] #{subjectstr}"
if ehtml_part != nil
html_part do
content_type("text/html; charset=# {type_html}")
#content_transfer_encoding("7bit")
body "# {bodystr_html}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part != nil
text_part do
content_type("text/plain; charset=# {type_plain}")
#content_transfer_encoding("7bit")
body "#{bodystr_plain}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
end
end
if eplain_part == nil && ehtml_part == nil
body "#{bodystr}\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
charset=type
end
#puts e.attachments.inspect
if e.attachments.length>0
e.attachments.each do |a|
add_file a.encoded
end
end
end
puts "1 email sent"
rescue => e
puts "error: #{e}"
$errorfile.write("#{Time.now}\nerror sending to #{tostr}: #{e},\nemail subject: #{subjectstr}\n\n")
$errorfile.flush()
end
end
end
end
$errorfile.close()

This is untested, and isn't really an attempt to find or fix the bug. It's to show how your code should look, written in more idiomatic Ruby code. And, as a result, it might fix the problem you're seeing. If not, at least you'll have a better idea how you should be writing your code:
require 'mail'
Define some constants for literal strings that get reused. Do this at the top so you don't have to search through the code to change things in multiple places, making it likely you'll miss one of them.
PATH_TO_FILES = "C:/Users/j.de_miguel/Desktop/mailman.forma"
BODY_BOILERPLATE_FORMAT = "%s\nmailman#forma.culturadigital.cc para darte de baja escribe \"baja\" a info#culturadigital.cc"
Group your methods toward the top of the file, after constants.
We open using 'a', not 'a+'. We don't need read/write, we only need write.
This opens and closes the file as necessary.
Closing the file automatically does a flush.
If you're calling the log method often then there are better ways to do this, but this isn't a heavyweight script.
I'm using File.join to build the filename based on the path. File.join is aware of the path separators and does the right thing automatically.
String.% makes it easy to create a standard output format.
def log(text)
File.open(File.join(PATH_TO_FILES, "error_log2.txt"), 'a') do |log_file|
log_file.puts "%s : %s" % [Time.now, text]
end
end
Method names in Ruby are snake_case, not CamelCase.
There's no reason to have multiple gsub! nor are the conditional tests necessary. If the sub-string you want to purge exists in the string gsub will do it, otherwise it moves on. Chaining the gsub methods reduces the code to one line.
gsub could/should probably be sub unless you know there could be multiple hits to be substituted in the string.
return is redundant so we don't use it unless we're explicitly returning a value to leave a block prematurely.
def delete_subject_recursion(subjstr)
subjstr.gsub(/.\[FORMA 2013\]/,"").gsub(/((?i)Re: ){2,}/, "Re: ")
end
Since registered is supposed to be a boolean, use any? to do the test. If any matches are found any? bails out and returns true.
def user_is_registered(mailaddr)
registered = subscribers.any?{ |s| mailaddr == s }
log("user #{ mailaddr } attempted to mailman") unless registered
registered
end
Use foreach to iterate over the lines of a file.
subscribers = []
File.foreach(File.join(PATH_TO_FILES, "subscribers2.txt")) do |line|
subscribers << line.chomp if line['#']
end
puts subscribers
log('')
Mail.defaults do
retriever_method(
:imap,
{
:address => "imap.1and1.es",
:port => 143,
:user_name => "mailman#somedomain.com",
:password => "xxxxxxxx",
:enable_ssl => false
}
)
delivery_method(
:smtp,
{
:address => "smtp.1and1.es",
:port => 587,
:domain => '1and1.es',
:user_name => 'mailman#somaedomain.com',
:password => 'xxxxxxxxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true
}
)
end
#emailslist=Mail.find(keys: ['NOT','SEEN'])
emailslist = [Mail.last]
emailslist.each do |e|
This use of ternary statements here is probably not desirable but I left it.
Formatting into columns makes it easier to read.
Organize your assignments and uses so they're not strewn all through the file.
eplain_part = e.text_part ? e.text_part.body.decoded : nil
type_plain = eplain_part ? e.text_part.charset.to_s : nil
ehtml_part = e.html_part ? e.html_part.body.decoded : nil
type_html = ehtml_part ? e.html_part.charset.to_s : nil
e_charset = e.charset
body_str = e_charset ? e.body.decoded.to_s.force_encoding(e_charset) : nil
e_charset = e_charset ? e_charset.to_s : type_plain
puts e_charset.inspect
subjectstr = e.subject.to_s.encode(e_charset)
fromstr = e.from.first.to_s.encode(e_charset)
puts fromstr
bodystr_plain = eplain_part ? eplain_part.force_encoding(type_plain) : nil
bodystr_html = ehtml_part ? ehtml_part.force_encoding(type_html) : nil
subscribers.each do |subscriber|
puts subscriber.inspect
if !subjectstr[/^\[FORMA 2013\]/] && user_is_registered(fromstr)
subjectstr = delete_subject_recursion(subjectstr)
begin
Mail.deliver do
from fromstr
to "mailman#somedomain.com"
bcc subscriber
subject "[FORMA 2013] #{ subjectstr }"
if ehtml_part
html_part do
content_type("text/html; charset=#{ type_html }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_html
end
end
if eplain_part
text_part do
content_type("text/plain; charset=#{ type_plain }")
#content_transfer_encoding("7bit")
body BODY_BOILERPLATE_FORMAT % bodystr_plain
end
end
if !eplain_part && !ehtml_part
body BODY_BOILERPLATE_FORMAT % body_str
charset = e_charset
end
#puts e.attachments.inspect
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
end
puts "1 email sent"
rescue => e
puts "error: #{ e }"
log("error sending to #{ subscriber }: #{ e },\nemail subject: #{ subjectstr }")
end
end
end
end
if e.attachments.length>0
e.attachments.each do |a|
add_file a
end
end
That can be refactored into a simple, single-line using a trailing conditional if test:
e.attachments.each { |a| add_file a.encoded } if e.attachments.length > 0
Using a single line like this is OK when you're doing something simple. Don't use them for more complex code because you'll induce visual noise, which makes it hard to understand and read your code.
But let's look at what the code above is actually doing. e.attachments in this context appears to be returning an array, or some sort of enumerable collection, otherwise each wouldn't work. length will tell us how many elements exist in the "array" (or whatever it is) that is returned by attachments.
If length is zero, then we don't want to do anything, so we could say:
e.attachments.each { |a| add_file a.encoded } unless e.attachments.empty?
(Assuming attachments implements an empty? method.)
That's kind of redundant too though. If e.attachments is empty already, what will each do? It would check to see if attachments returned an array containing any elements and if it's empty it'd skip its block entirely, effectively acting just like the trailing if condition was triggered. SOOOooo, we can use this instead:
e.attachments.each { |a| add_file a.encoded }
Ruby Style guides:
https://github.com/bbatsov/ruby-style-guide
https://github.com/styleguide/ruby
The second is based on the first.

The Tin Mans answer mostly works. I change how attachments were added since his version was not working for me.
e.attachments.each { |a| attachments[a.filename] = a.decoded } if e.attachments.length > 0

Related

Ruby If statement refactor

I'm coming back to Ruby after a long time away.
I've written the following code which is functional:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
if data['Line 1'].blank?
a.unstructured_address.line1 = nil
else
a.unstructured_address.line1 = data['Line 1']
end
if data['Line 2'].blank?
a.unstructured_address.line2 = nil
else
a.unstructured_address.line2 = data['Line 2']
end
if data['Line 3'].blank?
a.unstructured_address.line3 = nil
else
a.unstructured_address.line3 = data['Line 3']
end
if data['Line 4'].blank?
a.unstructured_address.line4 = nil
else
a.unstructured_address.line4 = data['Line 4']
end
if data['Line 5'].blank?
a.unstructured_address.line5 = nil
else
a.unstructured_address.line5 = data['Line 5']
end
if data['Postcode'].blank?
a.unstructured_address.postcode = nil
else
a.unstructured_address.postcode = data['Postcode']
end
end
end
Is there a way this can be re-written in a 'nicer' way in one loop so that I don't need all of these individual if statements.
Any advice would be greatly appreciated.
If data contains only expected values, you can convert key to the method name.
def generate_address_record(data)
address = FM[:fap_address].build do |a|
data.each do |key, value|
name = key.gsub(/[[:space:]]/, '').downcase
a.unstructured_address.public_send("#{name}=", value.presence)
end
end
end
Be careful(or don't use this approach) when hash keys are coming from outside of the application or other uncontrolled environment.
Yup, you can use #presence (I'm assuming you're using Rails):
a.unstructured_address.line1 = data['Line 1'].presence
#presence behaviour:
''.presence
# => nil
nil.presence
# => nil
'a'.presence
# => "a"
false.presence
# => nil
I propose this combination of the various already posted solutions because I think it has a good balance between shortness and readability:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
a.unstructured_address.line1 = data['Line 1'].presence
a.unstructured_address.line2 = data['Line 2'].presence
a.unstructured_address.line3 = data['Line 3'].presence
a.unstructured_address.line4 = data['Line 4'].presence
a.unstructured_address.line5 = data['Line 5'].presence
a.unstructured_address.postcode = data['Postcode'].presence
end
end
One pattern I find usefull is to make a hash and then iterate over it:
def generate_address_record(data)
address = FM[:fap_address].build do |a|
{
"Line 1" => :line1,
"Line 2" => :line2,
"Line 3" => :line3,
"Line 4" => :line4,
"Line 5" => :line5,
"Postcode" => :postcode
}.each do |key, accessor|
if data[key].blank?
a.unstructured_address.send(accessor) = nil
else
a.unstructured_address.send(:"#{accessor}=") = data[key]
end
end
end
end
You can also use this with presence as mrzasa shared

How to return to the calling method?

I have a program that uses a method for verification, if that verification failed I would like to return to the method it was called from, for example:
def obtain_pokemon_name
print 'Enter Pokemon: '
pokemon = gets.chomp.capitalize
obtain_basic_attack(pokemon)
end
def obtain_basic_attack(poke)
print 'Enter basic attack: '
basic_attack = gets.chomp.downcase
check_attacks(poke, basic_attack)
obtain_spec_attack(poke)
end
def obtain_spec_attack(poke)
print 'Enter special attack: '
spec_attack = gets.chomp.downcase
check_attacks(poke, spec_attack)
end
def check_attacks(pokemon, attack)
if POKEMON_ATTACKS[pokemon][attack] == nil
puts "#{attack} is not one of #{pokemon}'s attacks, try again.."
return # to where this function was called
else
attack
end
end
begin
obtain_pokemon_name
rescue => e
puts "Failed with error code: #{e}"
end
When this is run:
Enter Pokemon: arbok
Enter basic attack: eat
eat is not one of Arbok's attacks, try again..
Enter special attack: test
test is not one of Arbok's attacks, try again..
Attack list:
POKEMON_ATTACKS = {
'Bulbasaur' => {'tackle' => 10.9, 'vine whip' => 15.4, 'power whip' => 21.4, 'seed bomb' => 12.5, 'sludge bomb' => 19.2},
'Ivysaur' => {'razor leaf' => 10.3, 'vine whip' => 15.4, 'power whip' => 21.4, 'sludge bomb' => 19.2, 'solar beam' => 13.3},
'Kakuna' => {'bug bite' => 13.3, 'poison sting' => 10.3, 'struggle' => 8.8},
'Beedrill' => {'bug bite' => 13.3, 'poison jab' => 14.3, 'aerial ace' => 8.6, 'sludge bomb' => 19.2, 'x-scissor' => 14.3},
'Pidgey' => {'quick attack' => 7.5, 'tackle' => 10.9, 'aerial ace' => 8.6, 'air cutter' => 7.6, 'twister' => 5.6},
'Ekans' => {'acid' => 9.5, 'poison sting' => 10.3, 'gunk shot' => 20.0, 'sludge bomb' => 19.2, 'wrap' => 3.8},
'Arbok' => {'acid' => 9.5, 'bite' => 12.0, 'dark pulse' => 12.9, 'gunk shot' => 20.0, 'sludge wave' => 17.6},
}
So my question is, if the attack is not present in the data, how can I return back to the calling method? So for instance if I call arbok and his attack is tackle if it doesn't exist in the hash, how would I return to the obtain_basic_attack(poke) method?
RIght here:
puts "#{attack} is not one of #{pokemon}'s attacks, try again.."
return # to where this function was called
you should call the original method again. i.e.
if POKEMON_ATTACKS[pokemon][attack] == nil
puts "#{attack} is not one of #{pokemon}'s attacks, try again.."
return obtain_spec_attack(poke)
You could alternatively add this logic to obtain_spec_attack:
def obtain_spec_attack(poke)
loop do
print 'Enter special attack: '
spec_attack = gets.chomp.downcase
attack_found = check_attacks(poke, spec_attack)
if attack_found
break attack_found # this will return attack_found from the loop
else
puts "attack not found"
end
end
end
edit
looking at your question again, I realize you want to return to a method multiple levels up. You could use the approaches I've already outlined, or alternatively use rescue:
def obtain_basic_attack(poke)
begin
print 'Enter basic attack: '
basic_attack = gets.chomp.downcase
check_attacks(poke, basic_attack)
obtain_spec_attack(poke)
rescue AttackNotFoundError
retry # runs the 'begin' block again
end
end
def obtain_spec_attack(poke)
print 'Enter special attack: '
spec_attack = gets.chomp.downcase
check_attacks(poke, spec_attack)
end
def check_attacks(pokemon, attack)
if POKEMON_ATTACKS[pokemon][attack] == nil
puts "#{attack} is not one of #{pokemon}'s attacks, try again.."
raise AttackNotFoundError
else
attack
end
end
In order to use a custom error like AttackNotFoundError, you need to define the error class somewhere:
class AttackNotFoundError < StandardError; end
You could use any error really, such as raise StandardError, but it's better to restrict what errors you're rescuing so that you don't accidentally rescue an unrelated error.

Using field as input to Logstash Grok filter pattern

I'm wondering if it is possible to use a field in the Logstash message as the input the to Grok pattern. Say I have an entry that looks like:
{
"message":"10.1.1.1",
"grok_filter":"%{IP:client}"
}
I want to be able to do something like this:
filter {
grok {
match => ["message", ["%{grok_filter}"]]
}
}
The problem is this crashes Logstash as it appears to treat "%{grok_filter}" as the Grok filter itself instead of the value of grok_filter. I get the following after Logstash has crashed:
The error reported is:
pattern %{grok_filter} not defined
Is there anyway to get the value of a field from inside the Grok filter block and use that as the input to the Grok pattern?
The answer is no -- the grok filter compiles its pattern when the filter is initialized. If you need to do something like that you'll have to write your own filter that compiles the pattern every time (and pay the performance penalty).
Without knowing more about why you want to do this, it's hard to recommend the best course of action. If you have a limited number of patterns, you can just set a grok_filter_type parameter and then have a bunch of if [grok_filter_type] == 'ip' { grok { ... } } type of things.
Here's a custom filter that will allow you to do what you want -- it's mostly a copy of the grok code, but there are some changes/simplifications. I've tested it and it seems to work for me.
# encoding: utf-8
require "logstash/filters/base"
require "logstash/namespace"
require "logstash/environment"
require "set"
# A version of grok that can parse from a log-defined pattern. Not really
# recommended for high usage patterns, but for the occassional pattern it
# should work
# filter {
# grok_dynamic {
# match_field => "message"
# pattern_field => "message_pattern"
# }
# }
#
class LogStash::Filters::GrokDynamic < LogStash::Filters::Base
config_name "grok_dynamic"
milestone 1
# The field that contains the data to match against
config :match_field, :validate => :string, :required => true
# the field that contains the pattern
config :pattern_field, :validate => :string, :required => true
# where the patterns are
config :patterns_dir, :validate => :array, :default => []
# If true, only store named captures from grok.
config :named_captures_only, :validate => :boolean, :default => true
# If true, keep empty captures as event fields.
config :keep_empty_captures, :validate => :boolean, :default => false
# Append values to the 'tags' field when there has been no
# successful match
config :tag_on_failure, :validate => :array, :default => ["_grokparsefailure"]
# The fields to overwrite.
#
# This allows you to overwrite a value in a field that already exists.
config :overwrite, :validate => :array, :default => []
# Detect if we are running from a jarfile, pick the right path.
##patterns_path ||= Set.new
##patterns_path += [LogStash::Environment.pattern_path("*")]
public
def initialize(params)
super(params)
#handlers = {}
end
public
def register
require "grok-pure" # rubygem 'jls-grok'
#patternfiles = []
# Have ##patterns_path show first. Last-in pattern definitions win; this
# will let folks redefine built-in patterns at runtime.
#patterns_dir = ##patterns_path.to_a + #patterns_dir
#logger.info? and #logger.info("Grok patterns path", :patterns_dir => #patterns_dir)
#patterns_dir.each do |path|
if File.directory?(path)
path = File.join(path, "*")
end
Dir.glob(path).each do |file|
#logger.info? and #logger.info("Grok loading patterns from file", :path => file)
#patternfiles << file
end
end
#patterns = Hash.new { |h,k| h[k] = [] }
#grok = Grok.new
#patternfiles.each { |path| #grok.add_patterns_from_file(path) }
end # def register
public
def filter(event)
return unless filter?(event)
return if event[#match_field].nil? || event[#pattern_field].nil?
#logger.debug? and #logger.debug("Running grok_dynamic filter", :event => event);
#grok.compile(event[#pattern_field]);
if match(#grok,#match_field, event)
filter_matched(event)
else
# Tag this event if we can't parse it. We can use this later to
# reparse+reindex logs if we improve the patterns given.
#tag_on_failure.each do |tag|
event["tags"] ||= []
event["tags"] << tag unless event["tags"].include?(tag)
end
end
#logger.debug? and #logger.debug("Event now: ", :event => event)
end # def filter
private
def match(grok, field, event)
input = event[field]
if input.is_a?(Array)
success = true
input.each do |input|
match = grok.match(input)
if match
match.each_capture do |capture, value|
handle(capture, value, event)
end
else
success = false
end
end
return success
#elsif input.is_a?(String)
else
# Convert anything else to string (number, hash, etc)
match = grok.match(input.to_s)
return false if !match
match.each_capture do |capture, value|
handle(capture, value, event)
end
return true
end
rescue StandardError => e
#logger.warn("Grok regexp threw exception", :exception => e.message)
end
private
def handle(capture, value, event)
handler = #handlers[capture] ||= compile_capture_handler(capture)
return handler.call(value, event)
end
private
def compile_capture_handler(capture)
# SYNTAX:SEMANTIC:TYPE
syntax, semantic, coerce = capture.split(":")
# each_capture do |fullname, value|
# capture_handlers[fullname].call(value, event)
# end
code = []
code << "# for capture #{capture}"
code << "lambda do |value, event|"
#code << " p :value => value, :event => event"
if semantic.nil?
if #named_captures_only
# Abort early if we are only keeping named (semantic) captures
# and this capture has no semantic name.
code << " return"
else
field = syntax
end
else
field = semantic
end
code << " return if value.nil? || value.empty?" unless #keep_empty_captures
if coerce
case coerce
when "int"; code << " value = value.to_i"
when "float"; code << " value = value.to_f"
end
end
code << " # field: #{field}"
if #overwrite.include?(field)
code << " event[field] = value"
else
code << " v = event[field]"
code << " if v.nil?"
code << " event[field] = value"
code << " elsif v.is_a?(Array)"
code << " event[field] << value"
code << " elsif v.is_a?(String)"
# Promote to array since we aren't overwriting.
code << " event[field] = [v, value]"
code << " end"
end
code << " return"
code << "end"
#puts code
return eval(code.join("\n"), binding, "<grok capture #{capture}>")
end # def compile_capture_handler
end # class LogStash::Filters::Grok

I dont't understand boolean in ruby

My code, in my file spec
describe TestChamber::Account_v1 do
let(:agree) { nil }
let(:agree) { nil }
let(:account) do
TestChamber::Account_v1.new(
:agree => agree,
:agree1 => agree
)
end
context "when tested account v1" do
let(:agree) {true}
let(:agree1) {false}
it "test account" do
account.create_account
end
end
my file lib i get two argument then puts them in terminal
def initialize(options={})
#agree = options[:#agree]
#agree1 = options[:#agree1]
end
def create_account
if (#agree == true)
puts "abc"
end
if (#agree1 != false)
puts "abc1"
end
end
my output
abc1
anyone can explain for me this code?
You're setting:
#agree = options[:#agree]
#agree1 = options[:#agree1]
Replacing this with:
#agree = options[:agree]
#agree1 = options[:agree1]
should fix the assignment problem.
Futhermore, in you spec file there's:
let(:agree) { nil }
let(:agree) { nil }
The second one should probably be agree1. And further down:
TestChamber::Account_v1.new(
:agree => agree,
:agree1 => agree
)
Again, this should be :agree1 => agree1

What is a more readable way to test for one of three strings?

Is there a more readable way to test if delivery_status is one of three strings?
if ["partial", "successful", "unsuccessful"].include? delivery_status
Here's what I'd really like, but it doesn't work:
if delivery_status == ("partial" or "successful" or "unsuccessful")
While I would not advise this, you can do it anyway:
def String
def is_one_of?(array)
array.include?(self)
end
end
And then:
if delivery_status.is_one_of?([...])
But there is a much better solution: use case (if possible in your situation):
case delivery_status
when 'partial', 'successful', 'unsuccessful'
#stuff happens here
when ... #other conditions
end
if %w[partial successful unsuccessful].include? delivery_status
It's not intuitive, but using the Regexp engine can speed these tests up:
STATES = ["partial", "successful", "unsuccessful"]
regex = /\b(?:#{ Regexp.union(STATES).source })\b/i
=> /\b(?:partial|successful|unsuccessful)\b/i
delivery_status = 'this is partial'
!!delivery_status[regex]
=> true
delivery_status = 'that was successful'
!!delivery_status[regex]
=> true
delivery_status = 'Yoda says, "unsuccessful that was not."'
!!delivery_status[regex]
=> true
delivery_status = 'foo bar'
!!delivery_status[regex]
=> false
If I'm not searching a string for the word, I'll use a hash for a lookup:
STATES = %w[partial successful unsuccessful].each_with_object({}) { |s, h| h[s] = true }
=> {"partial"=>true, "successful"=>true, "unsuccessful"=>true}
STATES['partial']
=> true
STATES['foo']
=> nil
Or use:
!!STATES['foo']
=> false
If you want a value besides true/nil/false:
STATES = %w[partial successful unsuccessful].each_with_index.with_object({}) { |(s, i), h| h[s] = i }
=> {"partial"=>0, "successful"=>1, "unsuccessful"=>2}
That'll give you 0, 1, 2 or nil.
I ended up doing something similar to #Linuxios's suggestion
class String
def is_one_of(*these)
these.include? self
end
def is_not_one_of(*these)
these.include? self ? false : true
end
end
This allows me to write:
if delivery_status.is_one_of "partial", "successful", "unsuccessful"

Resources