Ruby and Rails Simple Question on Expression - ruby-on-rails

Hey there, is there a quick Ruby (or Rails) expression that returns nil if an object does not have a value ?
For instance, if self.name is null in database, it should return nil. If it's a string, it should return it. I'm thinking of unless, but maybe there is something better.
Also, if you have a function like get_profession(type), and type can be "first", "second" or "third", what would be an elegant way of inquiring self.first or self.second or self.third(according to type parameter) and return nil unless self.first(or other type) has a string value ?
As you see, the second question closely relates to the first.

In Ruby, if a variable does not have a value, it is nil by default. To answer your second question, you can use the send method to call your "type" methods. Something like,
def get_profession(type)
# Making sure correct type is passed.
raise RuntimeError unless ['first', 'second', 'third'].include?(type.to_s)
send(type)
end
Send expects a method in a string as parameter, which it calls on the same object. In your case if the response of your "type" method has no value, it would be by default be nil.

it actually returns nil if there was no value. have you actually tried it yet? if an object doesn't have any value it SHOULD return nil
need to add though that your attribute should have a corresponding column name in the database..otherwise it will be an undefined method call.
e.g.
Company.name < -- should return nil if there's a name column in your companies table but has no value
if you don't have a name column it'll say undefined method name.
It is common and cleaner to just check for a field if you're planning to display it like so:
<%= #person.profession if #person.profession %>
or the other way around
<%= #person.profession unless #person.profession.nil? %>

Related

Converting array with .join not saving value

I have a method do_stuff that takes a string as a value. However, an array of two strings is occasionally passed in. In this situation, I need to convert the array to a single string (without commas). So for example, ["hello", "world"] should become "hello world".
So, if value = array, join the two strings, otherwise leave it alone.
The following line I have does what I want, but I am struggling with actually "saving" the value before passing it to the method do_other_stuff.
def do_stuff(value)
value.join("") if value.is_a? Array
do_other_stuff(value)
end
So I think i am close, but what would be the best way to ensure value is manipulated before passing it to do_other_stuff ?
join does not change your object, you're wasting its return value
value = value.join if value.is_a? Array
Note that "" is the default for the join parameter, so I got rid of it
Replace
value.join("") if value.is_a? Array
With
value = value.join("") if value.is_a? Array
Basically you need to reassign result back to value
Use duck typing instead of checking the class:
def do_stuff(value)
do_other_stuff(value.try(:join, '') || value)
end
.try is from ActiveSupport and will return nil if the object does not respond to the method. In plain old ruby you would write this as:
def do_stuff(value)
do_other_stuff(value.respond_to?(:join) ? value.join("") : value)
end

how do i check that all attributes/values in a class or json object are nil

I have a method that retrieves an elasticsearch query from an API and converts it to an array of JSON objects.
In some cases, the last JSON object is not a nil object, but all the attributes have nil values.
I'd like to avoid mapping over every object, but I need to filter out those JSON objects that have all nil values.
for example:
=> [#<Api::User:0x006546546
#user_id=1,
#height=70,
#age=25>,
#<Api::User:0x006546542
#user_id=nil
#height=nil
#age=nil>]
I want to remove the all-nil object from the array either while it is in JSON format or after it is converted to an array of Api::User objects.
Is a map the only way to check all values of all objects or is there a less resource-intensive method?
I'd lean heavy on methods that Enumerable and Object give you. From Enumerable we can use select to choose only those elements of the array that meet some condition. That condition is that at least one of the instance variables is not nil, and for that we can use any?.
From Object we will use instance_variables to get a collection of instance variables, and we will access the value of those instance variables with instance_variable_get.
It would look something like this:
filtered_array = array.select do |object|
object.instance_variables.any? { |var| object.instance_variable_get(var) }
end
Of course any? is efficient in that it will stop iteration and return true on the very first "truthy" variable. As suggested by SteveTurczyn if you only need to check the last value, it is even quicker.
filtered_array = array[0..-2] if array[-1].instance_variables.none? { |var| object.instance_variable_get(var) }
Someone may come up with a better way, but it seems you don't need to check all values of all objects, as the first value you check that's non-nil means that object is good. I'd do something that checks each value up to the point that a non-nil value is found. It would also help efficiency if you can order tested fields with most likely to have a value first (e.g. user_id )
array.select do |object|
%i(user_id height age).find do |field_name|
object.send(field_name)
end
end
If it's only the last object that needs to be tested, then even easier...
array.pop if %i(user_id height age).find {|field_name| array.last.send(field_name) }
I'm sure there is a cleaner way, but just in case you need to map each object:
def are_attributes_not_nil?(my_object)
my_object.instance_variables.map{ |attr| my_object.send(attr.to_s[1..-1]).nil? }.uniq.include? false
end
returns false if all attributes are nil
My solution involves first implementing a way to convert the objects into hashes before turning them into the JSON itself.
api_user_array #=> [#<Api::User:0x006546546 ... >, #<Api::User:0x006546542 ... >]
api_user_array = api_user_array.map(&:to_h).reject { |hash| hash.values.all?(&:nil?) }
# should result in an array of hashes with at least one value not nil
This assumes that you implement the Api::User#to_h method. This could be something like this:
def attribute_names
%w[user_id height age]
end
def to_h
attribute_names.each_with_object({}) { |attr, hash| hash[attr] = send(attr) }
# assuming all attributes have a getter, otherwise
attribute_names.each_with_object({}) { |attr, hash| hash[attr] = instance_variable_get("##{attr}") }
end

ERROR: Nil Can't be Coerced into a Fixnum

I have the following function to sum all the records of an :amount field in my Pack model for that given user:
user.rb
def total_money_spent_cents
amount = self.packs.map(&:amount).sum
return amount
end
However, when I use this function I receive the following error:
nil can't be coerced into Fixnum
Any suggestions?
EDIT
I am still having issues in regards to Fixnum in my tests, and have another question open here.
This suggests that one of your packs has an amount field which has not yet been set, so is nil. When you try and add it to something else, it undergoes Type coercion, to see if Ruby can massage its type into one that can be added to numbers, but it can't, and so you have this error.
One solution is this:
def total_amount_spent_cents
packs.map(&:amount).compact.sum
end
Array#compact removes the nil elements.
This may be fixing the symptom and not the actual problem though. It could be the case that you shouldn't have nil's in there at all, in which case you should check the initialisation of your Pack model (or perhaps its validations, to ensure that amount is mandatory).
I added some extra methods into Array and Hash for this sort of thing: they're like compact but they remove all values returning true for blank? rather than just nil: so will remove empty strings, empty arrays, hashes etc.
class Hash
def compact_blank!
self.each{|k,v| self.delete(k) if v.blank? }
self
end
def compact_blank
self.dup.compact_blank!
end
end
class Array
def compact_blank!
self.delete_if(&:blank?)
end
def compact_blank
self.dup.compact_blank!
end
end
use like
["1", "abc", "", nil, []].compact_blank
=> ["1", "abc"]
it's useful with params especially, where you might get a lot of empty strings through.

For a hash of objects in ruby, hash.keys.index(obj) works but hash.has_key?(obj) does not. Why?

Here's my Rails class
class SkinnyEmployee
include ActiveModel::Validations
attr_accessor :uid, :name
validates :uid, :presence => true
def initialize(id, name)
#uid = id
#name = name
end
def ==(other)
puts "Calling =="
raise ArgumentError.new("other is nil or bad in "+self.to_s) if other.nil? or !other.instance_of?(SkinnyEmployee)
return (self.class == other.class && self.uid == other.uid)
end
alias :eql? :==
end
I have a hash of SkinnyEmployee objects. E.g.,
skinny_hash = {SkinnyEmployee.new("123", "xyz") => 1, SkinnyEmployee.new("456", "abc") => 2}
I have another SkinnyEmployee object that I want to look up. E.g.,
entry = SkinnyEmployee.new("456", "abc")
When I do
skinny_hash.keys.index(entry)
I get 1, as expected. But when I do
skinny_hash.has_key?(entry)
I get false.
Why is that? Doesn't has_key? also use == or eql? to find whether a key exists in a hash?
Thanks much for the help!
First, this drove me nuts. What you're doing looked absolutely correct to me, and, as you already know, doesn't work.
I can take you part of the way to a solution:
http://ruby-doc.org/core-2.0.0/Object.html#method-i-hash
quoting:
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The hash value is used along with eql? by the Hash class to determine if two objects reference the same hash key. Any hash value that exceeds the capacity of a Fixnum will be truncated before being used.
I added:
def hash
1
end
to your SkinnyEmployee Class, and has_key? started returning true. Obviously that's not a solution, but I'm thinking it at least puts you on the path to one.
You have overwritten the eql? method used by Array#index but not the hash method used by Hash#has_key?.
From Ruby docs for Object#hash
Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.
The Object#hash and Object#eql? methods return equal if and only if the objects occupy the same space in memory. Some classes like Array overwrite both methods to return true if the compared array's have same elements.
For your case you can define the hash method like:
def hash
"#{self.class}_#{self.uid}".hash
end
This would satisfy the docs criteria for hash method given above.
That is happening because the object you are using as a key and they one you are using to search the key are different.
Every time you call SkinnyEmployee.new it will create a new, different, object. For example
employee_1 = SkinnyEmployee.new("123", "xyz")
employee_2 = SkinnyEmployee.new("123", "xyz")
employee_1 == employee_1 #=> true
employee_2 == employee_2 #=> true
employee_2 == employee_1 #=> false
If you call object_id on both employee_1 and employee_2 you will notice that it gives you different id's.
Using has_key? will check for the exact same object, and that won't be the case if you use SkinnyEmployee.new("456", "abc").
You would need a way to retrieve the exact same object, store it in a variable or in the DB, you are using as a key and use it as an attribute for has_key? for it to work.
Hope this can help you.

Unwanted symbol to string conversion of hash key

When I assign in my controller
#my_hash = { :my_key => :my_value }
and test that controller by doing
get 'index'
assigns(:my_hash).should == { :my_key => :my_value }
then I get the following error message:
expected: {:my_key=>:my_value},
got: {"my_key"=>:my_value} (using ==)
Why does this automatic symbol to string conversion happen? Why does it affect the key of the hash?
It may end up as a HashWithIndifferentAccess if Rails somehow gets ahold of it, and that uses string keys internally. You might want to verify the class is the same:
assert_equal Hash, assigns(:my_hash).class
Parameters are always processed as the indifferent access kind of hash so you can retrieve using either string or symbol. If you're assigning this to your params hash on the get or post call, or you might be getting converted.
Another thing you can do is freeze it and see if anyone attempts to modify it because that should throw an exception:
#my_hash = { :my_key => :my_value }.freeze
You might try calling "stringify_keys":
assigns(:my_hash).should == { :my_key => :my_value }.stringify_keys
AHA! This is happening not because of Rails, per se, but because of Rspec.
I had the same problem testing the value of a Hashie::Mash in a controller spec (but it applies to anything that quacks like a Hash)
Specifically, in a controller spec, when you call assigns to access the instance variables set in the controller action, it's not returning exactly the instance variable you set, but rather, a copy of the variable that Rspec stores as a member of a HashWithIndifferentAccess (containing all the assigned instance variables). Unfortunately, when you stick a Hash (or anything that inherits from Hash) into a HashWithIndifferentAccess, it is automatically converted to an instance of that same, oh-so-convenient but not-quite-accurate class :)
The easiest work-around is to avoid the conversion by accessing the variable directly, before it's converted "for your convenience", using: controller.view_assigns['variable_name'] (note: the key here must be a string, not a symbol)
So the test in the original post should pass if it were changed to:
get 'index'
controller.view_assigns['my_hash'].should == { :my_key => :my_value }
(of course, .should is no longer supported in new versions of RSpec, but just for comparison I kept it the same)
See this article for further explanation:
http://ryanogles.by/rails/hashie/rspec/testing/2012/12/26/rails-controller-specs-dont-always-play-nice-with-hashie.html
I know this is old, but if you are upgrading from Rails-3 to 4, your controller tests may still have places where Hash with symbol keys was used but compared with the stringified version, just to prevent the wrong expectation.
Rails-4 has fixed this issue: https://github.com/rails/rails/pull/5082 .
I suggest updating your tests to have expectations against the actual keys.
In Rails-3 the assigns method converts your #my_hash to HashWithIndifferentAccess that stringifies all the keys -
def assigns(key = nil)
assigns = #controller.view_assigns.with_indifferent_access
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L10
Rails-4 updated it to return the original keys -
def assigns(key = nil)
assigns = {}.with_indifferent_access
#controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
key.nil? ? assigns : assigns[key]
end
https://github.com/rails/rails/blob/4-0-stable/actionpack/lib/action_dispatch/testing/test_process.rb#L7-L11
You can also pass your Hash object to the initializer of HashWithIndifferentAccess.
You can use HashWithIndifferentAccess.new as Hash init:
Thor::CoreExt::HashWithIndifferentAccess.new( to: 'mail#somehost.com', from: 'from#host.com')

Resources