Ruby Syntax: How to write "a = h[:v] if !h[:v].nil?" - ruby-on-rails

I find myself often writing statements equivalent to:
deleted_at = Time.at(data[:deleted_at]) if !data[:deleted_at].nil?
i'd like to be able to write this in a more concise way. Any suggestions?
Sometimes I write this as:
deleted_at = Time.at(i) if !(i = data[:deleted_at]).nil?
But I think this makes the code harder to read so I hope there is some nicer way to do this.

I use 'unless' since I find it more readable:
deleted_at = Time.at(data[:deleted_at]) unless data[:deleted_at].nil?
or you could even use:
deleted_at = Time.at(data[:deleted_at]) if data[:deleted_at]

You could wrap that generically into a lambda block:
class Object
def unless_nil?
yield self unless self.nil?
end
end
data[:deleted_at].unless_nil? {|i| deleted_at = i }
I'd recommend to not monkey patch the Object class but better create a module and include that in the classes you need this functionality for.

data_deleted = data[:deleted_at]
deleted_at = Time.at(data_deleted) unless data_deleted.nil?
makes it more readable IMO.

Not having deleted_at declared like that is not my cup of tea. It would at least be set to nil with something like this:
deleted_at = Time.at(data[:deleted_at]) rescue nil
or
deleted_at = data[:deleted_at].nil? ? nil : Time.at(data[:deleted_at])

You can take advantage of the nil version evaluating to false to do this:
deleted_at = data[:deleted_at] and Time.at(data[:deleted_at])
Although now having written this i think i prefer the if version.
Code is written far less than its read - concise doesn't always equate to readable.

You can also do
data[:deleted_at] and deleted_at = Time.at(data[:deleted_at])
although whether that's better is a matter of taste.

If it's only Time.at that's giving you problems, you could write
class Time
def nil_friendly_at(time)
return nil if time.nil? # Or "unless time"
at(time)
end
end
Or you could monkey-patch Time.at itself if you want (I'm not recommending this though!)

Related

Better way to check 2 conditions based on 1 object

I commonly need to use this kind of methods where I have to check if an object exists and if this object returns a specific value or a behavior. Is that a better way to write this code?
def set_current_theme
if current_tenant && current_tenant.has_custom_domain?
#theme = Theme.last || Theme.create
end
end
At a first glance, I would just add one conditional: if current_tenant.has_custom_domain? and that should be enough. But the result is generally that there is no such method (in this case has_custom_domain?) for nil class.
Shorter (and i think better) way is to use &. (it's shorthand for try!) like this.
if current_tenant&.has_custom_domain?
#theme = Theme.last || Theme.create
end
What does &. (ampersand dot) mean in Ruby?
I would suggest early return (so called guard clause) instead of :if statement, because you don't have :else clause:
def set_current_theme
return unless current_tenant&.has_custom_domain?
#theme = Theme.last || Theme.create
end

Rails/Ruby one-liner unless zero/nil?

Is there a way to make this situation more compact in rails views?
Eg I have haml
= object.count unless object.count ==0
I sort of don't like that has I'm repeating the function there, I would much rather have something like
= object.count unless ==0
Eg if I had more complex statements
= object.relations.where(attribute: "something").count unless zero?
I could split that into two lines say
- cnt = object.relations.where(attribute: "something").count
= cnt unless cnt==0
But for each situation I would have multiple lines, and storing a variable to use once sucks.
EDIT: just to elaborate I want to check if the number is 0, and if so not display anything. It looks nicer in the view that way.
UPDATE:
One of the answers made come up with a solution along these lines
class Object
def unless
self unless yield(self)
end
end
So I can call whatever object I have with a block eg. .unless{|c| c<1}
This lets me tack the conditionals on, and keeps it pretty clear what is going on :), bonus is as it's block driven I can use this on any object :P.
Thanks everyone :)
UPDATE EVEN MORE
Having |c| in the block sucked. So I looked up the api and changed it too
class Object
def unless(&block)
self unless instance_eval(&block)
end
end
So now I can use .count.unless{zero?} to accomplish this :P. Or if I have a complicated condition I can add that in with |c| etc.
If object is an array you can use object.empty? (or object.any? for the reverse case)
Just create a view helper:
def display_count_or_nothing(array)
array.count unless array.count == 0
end
In the view you can use it like this:
<%= display_count_or_nothing(array) %>
i think the following is nice and clear, although i hate the variable "object",
it would be much nicer if the name of the variable described the contents of the array (as plural)
= object.count unless object.empty?
If this is only about count, you can monkey patch Enumerable:
module Enumerable
def count_or_empty_string
self.any? ? self.count : ''
end
end
If object is an enumerable, you can do this:
= object.count_or_empty_string
This will return an "" if object.count == 0 else it will return an integer. So there is no need for unless or if in your HAML anymore.

Force Datatype in ActiveRecord with Select

I'm running a query with joins and conflicting column names. IE:
results = TableA.joins(:table_b).select('table_a.id as table_a_id', 'table_b.id as table_b_id')
In my result, table_a_id and table_b_id are both strings. What can I do to make them integers?
I feel like theres probably a way to get this to return results[0]['table_a']['id'] and results[0]['table_b']['id'] where both have the correct datatype, but I don't know how to do it.
My primary concern is making it so I can access both columns without running a second query.
Thanks!
It works for me. I do this :
comments = Comment.joins(:project).select('comments.id as table_a_id, projects.id as table_b_id')
And I could do this :
comments.first.table_a_id
To convert column in integer you can use sql functions. To convert in integer you can use sql or ruby functions.
Since Dougui's answer pretty much covers accessing both columns, I'll just address string-to-integer conversion.
You have two options, converting in SQL or in Ruby.
In Ruby, you can call .to_i on a string to turn it into an integer, thus:
> foo = '156'
=> '156'
> foo.to_i
=> 156
In SQL, you use the CAST function, thus:
SELECT CAST('156' AS INTEGER);
int4
------
156
(1 row)
It's a bit late, but maybe it could be useful.
I ran in the same issue, I solved doing something like this:
Starting from the answer from #Dougui, you can force the table_b_id type defining:
class TableA
def [](name)
if name.to_sym == :table_b_id
super.to_i
else
super
end
end
def method_missing(name, *args)
if name.to_sym == :table_b_id && args.empty? && !block_given?
super.to_i
else
super
end
end
end

How can I refactor this Ruby and Rails code?

How can I refactor this code?
if env["rack.request.form_hash"] && env["rack.request.form_hash"]["authenticity_token"]
env["rack.request.form_hash"]["authenticity_token"]=env["rack.request.form_hash"]["authenticity_token"].gsub("\r\n",'')
end
env["rack.request.form_hash"]["authenticity_token"] = env["rack.request.form_hash"]["authenticity_token"].gsub("\r\n",'') rescue nil
or with in place editing
env["rack.request.form_hash"]["authenticity_token"].gsub!("\r\n",'') rescue nil
if you have the andand gem, you can skip the check and go straight to:
env["rack.request.form_hash"]["authenticity_token"].andand.gsub("\r\n",'')
The hash indexes seem to be reused everywhere, maybe you can start there.
key1 = "rack.request.form_hash"
key2 = "authenticity_token"
env[key1] && env[key1][key2]
Nothing clever, but significantly shortens the line.
Something like this could work:
env[key1][key2].gsub!('\r\n','') if env.has_key?(key1) && env[key1].has_key?(key2)
I would recommend:
if (rrf = env["rack.request.form_hash"]) && rrf_at = rrf["authenticity_token"] then rrf_at.gsub!("\r\n",'') end
or similar but shorter:
rrf_at.gsub!("\r\n",'') if (rrf = env["rack.request.form_hash"]) && rrf_at = rrf["authenticity_token"]
It's DRY, concise and does not use rescue "hacks" ;-D
Rather then using andand or try, I would do:
if env.fetch("rack.request.form_hash", {})["authenticity_token"].to_s.gsub("\r\n",'')
or add to_hash to the inventory of useful NilClass methods (to_a, to_s, to_i, etc):
class NilClass; def to_hash; {} end end
and do:
if env["rack.request.form_hash"].to_hash["authenticity_token"].to_s.gsub("\r\n",'')

If string is empty then return some default value

Often I need to check if some value is blank and write that "No data present" like that:
#user.address.blank? ? "We don't know user's address" : #user.address
And when we have got about 20-30 fields that we need to process this way it becomes ugly.
What I've made is extended String class with or method
class String
def or(what)
self.strip.blank? ? what : self
end
end
#user.address.or("We don't know user's address")
Now it is looking better. But it is still raw and rough
How it would be better to solve my problem. Maybe it would be better to extend ActiveSupport class or use helper method or mixins or anything else. What ruby idealogy, your experience and best practices can tell to me.
ActiveSupport adds a presence method to all objects that returns its receiver if present? (the opposite of blank?), and nil otherwise.
Example:
host = config[:host].presence || 'localhost'
Phrogz sort of gave me the idea in PofMagicfingers comment, but what about overriding | instead?
class String
def |(what)
self.strip.blank? ? what : self
end
end
#user.address | "We don't know user's address"
Since you're doing this in Ruby on Rails, it looks like you're working with a model. If you wanted a reasonable default value everywhere in your app, you could (for example) override the address method for your User model.
I don't know ActiveRecord well enough to provide good code for this; in Sequel it would be something like:
class User < Sequel::Model
def address
if (val=self[:address]).empty?
"We don't know user's address"
else
val
end
end
end
...but for the example above this seems like you'd be mixing view logic into your model, which is not a good idea.
Your or method might have some unwanted side-effects, since the alternative (default) value is always evaluated, even if the string is not empty.
For example
#user.address.or User.make_a_long_and_painful_SQL_query_here
would make extra work even if address is not empty. Maybe you could update that a bit (sorry about confusing one-liner, trying to keep it short):
class String
def or what = ""
self.strip.empty? ? block_given? ? yield : what : self
end
end
#user.address.or "We don't know user's address"
#user.address.or { User.make_a_long_and_painful_SQL_query_here }
It is probably better to extend ActiveRecord or individual models instead of String.
In your view, you might prefer a more explicit pattern like
#user.attr_or_default :address, "We don't know the user's address"
Ruby:
unless my_str.empty? then my_str else 'default' end
RoR:
unless my_str.blank? then my_str else 'default' end
I recommend to use options.fetch(:myOption, defaultValue) because it works great with boolean flags like the ones mentioned above and therefore seems better to use in general.
Examples
value = {}
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = false
puts !!(value.fetch(:condition, true)) # Print false
value = {}
value[:condition] = true
puts !!(value.fetch(:condition, true)) # Print true
value = {}
value[:condition] = nil
puts !!(value.fetch(:condition, true)) # Print false

Resources