Convert an empty array to nil inplace - ruby-on-rails

I have the following code:
some_array = [] # Sometimes is filled, in this case it isn't
new_array = some_array || ['default', 'array', 'values']
Now, the || is not triggered, because [] != nil
Of course it can be solved by doing:
new_array = some_array
new_array = ['default', 'array', 'values'] if new_array.blank?
But I remember there is a function that does this in a single line, like:
[].filled_arr_or_nil # nil
['something'].filled_arr_or_nil # ['something']

I wasn't able to find the answer using a search engine, but StackOverflow gave me the answer with a similar question, but then about strings:
Converting an empty string to nil in place?
The solution is to use presence
Only available within Rails.

You can use something like this
some_array = []
new_array = some_array.empty? ? ['default', 'array', 'values'] : some_array
This is just a conditional statement that uses empty? to check if some_array contains any elements and then returns the desired output

Related

Is there an equivalent method for Ruby's `.dig` but where it assigens the values

Let's say we're using .dig in Ruby like this:
some_hash = {}
some_hash.dig('a', 'b', 'c')
# => nil
which returns nil
Is there a method where I can assign a value to the key c if any of the other ones are present? For example if I wanted to set c I would have to write:
some_hash['a'] = {} unless some_hash['a'].present?
some_hash['a']['b'] = {} unless some_hash['a']['b'].present?
some_hash['a']['b']['c'] = 'some value'
Is there a better way of writing the above?
That can be easily achieved when you initialize the hash with a default like this:
hash = Hash.new { |hash, key| hash[key] = Hash.new(&hash.default_proc) }
hash[:a][:b][:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}
Setting nested values in that hash with nested defaults can partly be done with dig (apart from the last key):
hash.dig(:a, :b)[:c] = 'some value'
hash
#=> {:a=>{:b=>{:c=>"some value"}}}

Elegant way to check if first method return nil and apply another method

I have the following:
options = args.select{ |arg| arg.respond_to?(:keys) }.first.dup.keep_if {|k| filter_keys.include? k}
EDIT
options = args.select{ |arg| arg.respond_to?(:keys) }.first.select {|k| filter_keys.include? k}
first sometimes returns nil which makes dup raises an exception. So I want an elegant way to check if the first returns nil or no before calling dup.
So if args is as follows:
[{:collection=>["abe", "<mus>", "hest"], :include_blank=>true}]
and filter_keys is as follows:
filter_keys = %w(include_blank required)
The result would be:
{:include_blank=>true}
Sometimes args would be like:
[[["<Africa>", [["<South Africa>", "<sa>"], ["Somalia", "so"]]], ["Europe", [["Denmark", "dk"], ["Ireland", "ie"]]]]]
which has no match at all.
or maybe like:
[nil, {:collection=>["abe", "<mus>", "hest"], :prompt=>true, :include_blank=>true, :multiple => 'ss'}]
which gives also
{:include_blank=>true}
My trial to this is as follows:
if f = args.select{ |arg| arg.respond_to?(:keys) }.first
options = f.select {|k| filter_keys.include? k}
end
This code is supposed to filter the args array to get only the matched keys from any hash within it.
But I don't see this code as simple nor elegant at all, since it allocates a new variable.
Firstly, I think args.select{ |arg| arg.respond_to?(:keys) }.first can be written as args.detect{ |arg| arg.respond_to?(:keys) }.
I'd like this way
arg = args.detect{|arg| arg.respond_to?(:keys) }
options = arg.select{|k| filter_keys.include? k} if arg
You can write is like:
args.detect { |arg| arg.is_a?(Hash) }.select { |k| filter_keys.include?(k.to_s) }
keep in mind on k.to_s because you provide the filter key list with strings:
filter_keys = %w(include_blank required)
but you have symbols in the args array

TypeError: no implicit conversion of Symbol into Integer

I encounter a strange problem when trying to alter values from a Hash. I have the following setup:
myHash = {
company_name:"MyCompany",
street:"Mainstreet",
postcode:"1234",
city:"MyCity",
free_seats:"3"
}
def cleanup string
string.titleize
end
def format
output = Hash.new
myHash.each do |item|
item[:company_name] = cleanup(item[:company_name])
item[:street] = cleanup(item[:street])
output << item
end
end
When I execute this code I get: "TypeError: no implicit conversion of Symbol into Integer" although the output of item[:company_name] is the expected string. What am I doing wrong?
Your item variable holds Array instance (in [hash_key, hash_value] format), so it doesn't expect Symbol in [] method.
This is how you could do it using Hash#each:
def format(hash)
output = Hash.new
hash.each do |key, value|
output[key] = cleanup(value)
end
output
end
or, without this:
def format(hash)
output = hash.dup
output[:company_name] = cleanup(output[:company_name])
output[:street] = cleanup(output[:street])
output
end
This error shows up when you are treating an array or string as a Hash. In this line myHash.each do |item| you are assigning item to a two-element array [key, value], so item[:symbol] throws an error.
You probably meant this:
require 'active_support/core_ext' # for titleize
myHash = {company_name:"MyCompany", street:"Mainstreet", postcode:"1234", city:"MyCity", free_seats:"3"}
def cleanup string
string.titleize
end
def format(hash)
output = {}
output[:company_name] = cleanup(hash[:company_name])
output[:street] = cleanup(hash[:street])
output
end
format(myHash) # => {:company_name=>"My Company", :street=>"Mainstreet"}
Please read documentation on Hash#each
myHash.each{|item|..} is returning you array object for item iterative variable like the following :--
[:company_name, "MyCompany"]
[:street, "Mainstreet"]
[:postcode, "1234"]
[:city, "MyCity"]
[:free_seats, "3"]
You should do this:--
def format
output = Hash.new
myHash.each do |k, v|
output[k] = cleanup(v)
end
output
end
Ive come across this many times in my work, an easy work around that I found is to ask if the array element is a Hash by class.
if i.class == Hash
notation like i[:label] will work in this block and not throw that error
end

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.

How do you call an index in .each_with_index

Not sure this isn't working.
>> params[:payments]
{"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"}, "1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}
So I can call a specific object with this :
>> params[:payments][:"1"]
{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}
But if I write this..
>> params[:payments].each_with_index{|item, idx| item[:"#{idx}"]}
TypeError Exception: Symbol as array index
Idealistically, I want to accomplish this :
params[:payments].each_with_index do |item, idx|
#calc.payments[idx][:date_paid] = item[:"#{idx}"][":amount_paid"]
#calc.payments[idx][:amount_paid] = (item[:"#{idx}"][":amount_paid"]).to_f
end
Update:
Based on some answers, I'ved tried this :
params[:payments].each{|k,v| #calc.payments[k.to_i] = v[":amounts_paid"]}
This turns #calc.payments into :
nil
nil
Backing up though, the others seem to work..
>> params[:payments].each{|k,v| p v[":amount_paid"]}
"80.00"
"100.00"
And this one..
>> params[:payments].each{|k,v| p #calc.payments[k.to_i]}
{:date_awarded=>"1/2/2008", :judgement_balance=>1955.96}
nil
How can I access item[idx] in a loop?
params[:payments].each do |k,v|
puts "Item %d amount=%s date=%s\n" % [k, v[":amount_paid"], v[":date_paid"]]
end
Item 0 amount=80.00 date=2/27/2008
Item 1 amount=100.00 date=5/8/2008
Update:
Ok, ok, here is a complete program .. script .. that you can actually run. Since you are trying to make sense of Ruby I think you should work with it outside of Rails for a few minutes. I mocked up #calc.payments, whatever that is. This code will run and apparently do what you want.
require 'pp'
(params = {})[:payments] = {"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"}, "1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}
pp params
class T; attr_accessor :payments; end
(#calc = T.new).payments = []
params[:payments].each do |k,v|
i = k.to_i
#calc.payments[i] ||= {}
#calc.payments[i][:date_paid] = v[":date_paid"]
#calc.payments[i][:amount_paid] = v[":date_paid"].to_f
end
pp #calc.payments
If you run it you should see:
{:payments=>
{"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"},
"1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}}
[{:date_paid=>"2/27/2008", :amount_paid=>2.0},
{:date_paid=>"5/8/2008", :amount_paid=>5.0}]
You could just do a this to access the values. Since params[:payments] contains a hash, then for each pass through, key, will be assigned the "0", "1", etc., and value will be assigned the hash with amount_paid and date_paid.
params[:payments].each do |key, value|
amount_paid = value[":amount_paid"]
...
end

Resources