Issue between Unless .blank? and if - ruby-on-rails

Take a look at the following code snippets. Keep in mind that #product.name is empty.
Code A
<% if #product.name %>
<div class="bonus">
<h4>Bonus</h4>
<%= #product.name %>
</div>
<% end %>
Code B
<% unless #product.name.blank? %>
<div class="bonus">
<h4>Bonus</h4>
<%= #product.name %>
</div>
<% end %>
From my understanding on how to use if and unless (thanks to this article), both Code A and B should have the exact same function, except that Code A is the correct way of doing it according to best practices.
The problem with this is though that Code A displays generates the <h4>Bonus</h4> tag whereas Code B does not!
How is this possible?

That's because in Ruby, an empty string is true.
Only false and nil evaluate to false, everything else is true in Ruby, including an empty string (unlike languages like Perl or PHP).
That's why Rails introduced blank? (and later its opposite present?)
From Rails' source code:
# An object is blank if it's false, empty, or a whitespace string.
# For example, "", " ", +nil+, [], and {} are all blank.
#
# This simplifies:
#
# if address.nil? || address.empty?
#
# ...to:
#
# if address.blank?
def blank?
respond_to?(:empty?) ? empty? : !self
end
# An object is present if it's not <tt>blank?</tt>.
def present?
!blank?
end

They're not the same.
blank? checks for nil and white-space.
if checks for truthiness. A string with only whitespace, including an empty string, is truthy.
The equivalent if statement would be if #product.name.present?

unless X.blank? is not the opposite of if X!
#product.name is truthy unless it is set to false or nil. If it contains an empty string, program flow will still enter the if block and <%= #product.name %> will output an empty string.
.blank? is true for empty strings or strings which contain only whitespace.

In Ruby only nil and false are considered false values. So if condition in if statement is not nil or false the code inside will be executed. In your example A #product.name is empty string that is considered as true value by Ruby. So the code is executed.
On the other hand there is blank? method introduced by Rails. It returns false for:
nil
false
'' (empty String)
' ' (whitespaced String)
{} (empty Hash)
[] (empty Array)
That's why you get false from blank? for empty #product.name.
And the opposite for blank? is present? method.

puts 'Empty string is truthy' if ''
#=> Empty string is truthy
That said, you can do:
<% if #product.name? %>
<% if #product.name.present? %>
<% unless #product.name.blank? %>
With a question mark at the end. That means "if #product.name is present" (<=> is not blank)
My preference goes to the first (#product.name?)

Related

Unexpected trouble comparing numbers

I'm writing a helper method that adds a class to an element depending on whether two numbers are equal to eachother. My code is as follows:
<% for note in #company.convertible_notes.each %>
<% if note.id %>
<li class="tab <%= note_nav(params, note.id) %>"><%= link_to "#{note.security_series} #{note.security_class} Note", convertible_note_convertible_notees_path(note) %></li>
<% end %>
<% end %>
note_nav calls the following helper:
def note_nav(params, note)
"active" if params[:controller]=="convertible_notees" && note.to_s==params[:converible_note_id].to_s
end
Now the surprising thing is that I cannot get the expression note.to_s==params[:converible_note_id].to_s to register true. Even when I know the two numbers being compared are both "1". I checked it using my log:
logger.debug "are they equal? #{note.to_s==params[:converible_note_id].to_s} note.id is #{note} note params are #{params[:convertible_note_id]}"
Which yields the following log entry:
are they equal? false note.id is 1 note params are 1
I would guess that they're two different types but given that I've converted both of them to_s, I don't know how that would be an issue. I've used this exact same technique on a few combinations of other models and have been completely error free. Any idea as to what might be going on?
Thanks in advance
Look at your test
"are they equal? #{note.to_s==params[:converible_note_id].to_s} note.id is #{note} note params are #{params[:convertible_note_id].to_s}"
:converible_note_id and :convertible_note_id are other keys, a type error
You could also get that output if one of the variables contains non-printing characters:
note = "1\000"
params = {
convertible_note_id: 1
}
puts "are they equal? #{note.to_s==params[:convertible_note_id].to_s}"
puts "note.id is #{note} note params are #{params[:convertible_note_id]}"
--output:--
are they equal? false
note.id is 1 note params are 1
To see what's really in a string, you should always use inspect():
p note.to_s, params[:convertible_note_id].to_s
--output:--
"1\u0000"
"1"

Rails write variable in view without using <%= %>

Let's say I've got the variable #var. Usually I would use <%= #var %> to write it into the view. Now I want to call a module method from within my view which internally decides to write the content of #var or not.
Inside the module I can't use <%= %>. How can I print the content of #var from within the module? The method would be called like this: <% my_method %>. Thanks
Update
Thanks for the answers so far. Maybe I should say more about my initial problem to be more clear. Sorry if I wasn't clear enough.
At first I used the <%= %> tag like this:
def print_if_present(var)
var ? var : ""
end
<%= print_if_present var %>
But then, when the var was nil, I got "" as output, which took space in the view. How can I prevent this behavior?
I assume that your module is actualy the view helper. If is that so, simply return var.
def my_method
if my_condition
#var
else # else clause is optional
#other_var
end
end
Note that the else clause is optional. If you want to write something or nothing, you can simply use the if. This is so because if the if is not executed and there is no else, it will return nil, that will be casted to an empty string in your template. Just to ilustrate,
if true
1
end
=> 1 #return if last value
if false
1
end
=> nil # return nil because there is no else block
Since you still want to print the return of your method on your template, you need to keep the equal sign:
<%= my_method %>
The best way to do this is to have your method return the string and use <%= ... %> as in fotanus’ answer, but in Rails if you really need to write output directly from a helper you could use the concat method:
The preferred method of outputting text in your views is to use the <%= “text” %> eRuby syntax. The regular puts and print methods do not operate as expected in an eRuby code block. If you absolutely must output text within a non-output code block (i.e., <% %>), you can use the concat method.
So you can define a helper like this:
def my_method
if some_condition
concat "Something or other"
else
concat "Something else"
end
end
And then use it in a non-output block:
<% my_method %>

If, else dynamic statement logic

Can someone explain the logic behind this code?(This is the correct code btw)
<% if #request.query['first_name'] && !#request.query['first_name'].empty? %>
Welcome! <%= #request.query['first_name'] %>
<% else %>
Hi! What is your name?
<% end %>
My intuition is to write the following instead:
<% if #request.query.inspect['first_name'].empty? %>
Hi! What is your name?
<% else %>
Welcome! <%= #request.query.inspect['first_name'] %>
<% end %>
I am trying to have a user form where people can input their names, when there is no input yet the text above the form says "Hi! What is your name?" when there is an input it has a message saying "Welcome! *User_name*"
The first block of code is not intuitive to me, the second one would make more sense.. ANy advice on how to understand the code?
Your intuition is correct, though you need an alternative to empty?. Rails adds a few different methods you can use:
blank? returns true if the receiver is nil, an empty array, string, or hash, or a string with only whitespace.
present? returns true if blank? is false. So your condition could be:
<% if #request.query['first_name'].present? %>
Welcome...
(I find it's always more intuitive to start with the positive condition - it would work just as well to check blank?).
Edit: It's pretty likely you can skip the query method entirely if all you expect there is either a string or nil. Just use:
<% if #request.query['first_name'] %>
You need to check if it's nil before you can check if its empty, because you are checking a Hash#empty?
irb(main):001:0> nil.empty?
NoMethodError: undefined method `empty?' for nil:NilClass
from (irb):1
irb(main):002:0> {}.empty?
=> true
The code checks for hash key existence, then check if the value of the hash is present. This action can be done in one check using:
#request.query.try(:[], 'first_name').empty?
You can avoid the first condition inside the if statement by transforimng nil into an empty string. I don't know if that is what you meant to do but you almost had.
First, you shouldn't call inspect in the hash because it will transform the entire thing into a 'complex' string. What you want to do turn only the value inside the first_name option, because in that case if the name exists it will still be the same, and if it doesn't, it will be turned into "nil".
Secondly, the method inspect isn't the best choice here, because the returned string will never be empty, given that nil.inspect => "nil". What you should use is the method to_s, wich will behave like this when applied to nil: nil.to_s => "".
Finally, you could update your code to:
<% if #request.query['first_name'].to_s.empty? %>
Hi! What is your name?
<% else %>
Welcome! <%= #request.query['first_name'] %>
<% end %>

Conditional statement in Rails

I'm trying to get some code to display based on a condition. I have a boolean field in a table called "show_weekly". What I'm attempting is: If the column == 1 then display the first line else display the 2nd line of code. for some reason it's only showing the 2nd line.
<% if #listing.show_weekly == 1 %>
<%= number_to_currency(#listing.price/4, :unit => "£") %> / week
<% else %>
<%= number_to_currency(#listing.price, :unit => "£") %> / month
<% end %>
any help is greatly appreciated. thanks
The value of a boolean column will either be false or true in ruby, not 0 or 1. So you should do:
<% if #listing.show_weekly %>
instead of <% if #listing.show_weekly == 1 %>
I'd suggest adding a method to your model
def print_price
#p = this.show_weekly? ? this.price / 4 : this.price
number_to_currency (#p, :unit => "£")
end
you need to check if it equals true, not 1
A boolean value is stored as a 1 or a 0, but it is always interpreted as true or false before being returned from the model. This is a source of a lot of confusion.
Using the accessor method show_weekly? can help because a question-mark in a method name usually indicates it will return a boolean value, or something that can be evaluated as boolean.
It will be a lot more readable if you have:
<% if #listing.show_weekly? %>
...
<% else %>
...
<% endif %>
Wherever possible, avoid comparing to specific hard-coded values. For instance, the following is redundant and yet I see it all the time:
# Example: Comparison to nil
if (#something == nil)
# Should be: Checking if initialized
if (#something)
# Example: Comparison to true
if (#something == true)
# Should be: Testing directly
if (#something)
As there are only two values that evaluate as false in Ruby, nil and false, it is generally the case that anything that is not true is nil. Occasionally you will have boolean columns that can be true, false or nil if it is not defined, but this is unusual and can trip up people.
If #listing.show_weekly contains a boolean value, just test for true :
<% if #listing.show_weekly %>
.....
<% else %>
....
<% end %>
Notice you don't even need the "== true". This is because the IF statement only looks to see if there's a true value returned from whatever expression follows it. If #listing.show_weekly contains a value of true then that's all the IF statement sees if that's all you provide it.

optional local variables in rails partial templates: how do I get out of the (defined? foo) mess?

I've been a bad kid and used the following syntax in my partial templates to set default values for local variables if a value wasn't explicitly defined in the :locals hash when rendering the partial --
<% foo = default_value unless (defined? foo) %>
This seemed to work fine until recently, when (for no reason I could discern) non-passed variables started behaving as if they had been defined to nil (rather than undefined).
As has been pointed by various helpful people on SO, http://api.rubyonrails.org/classes/ActionView/Base.html says not to use
defined? foo
and instead to use
local_assigns.has_key? :foo
I'm trying to amend my ways, but that means changing a lot of templates.
Can/should I just charge ahead and make this change in all the templates? Is there any trickiness I need to watch for? How diligently do I need to test each one?
I do this:
<% some_local = default_value if local_assigns[:some_local].nil? %>
Since local_assigns is a hash, you could also use fetch with the optional default_value.
local_assigns.fetch :foo, default_value
This will return default_value if foo wasn't set.
WARNING:
Be careful with local_assigns.fetch :foo, default_value when default_value is a method, as it will be called anyway in order to pass its result to fetch.
If your default_value is a method, you can wrap it in a block: local_assigns.fetch(:foo) { default_value } to prevent its call when it's not needed.
How about
<% foo ||= default_value %>
This says "use foo if it is not nil or true. Otherwise assign default_value to foo"
I think this should be repeated here (from http://api.rubyonrails.org/classes/ActionView/Base.html):
If you need to find out whether a certain local variable has been assigned a value in a particular render call, you need to use the following pattern:
<% if local_assigns.has_key? :headline %>
Headline: <%= headline %>
<% end %>
Testing using defined? headline will not work. This is an implementation restriction.
In my case, I use:
<% variable ||= "" %>
in my partial.
I don't have idea if that is good but for my is OK
I know it's an old thread but here's my small contribution: i would use local_assigns[:foo].presence in a conditional inside the partial.
Then i set foo only when needed in the render call:
<%= render 'path/to/my_partial', always_present_local_var: "bar", foo: "baz" %>
Have a look at te official Rails guide here. Valid from RoR 3.1.0.
This is a derivative of Pablo's answer. This allows me to set a default ('full'), and in the end, 'mode' is set in both local_assigns and an actual local variable.
haml/slim:
- mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full')
erb:
<% mode ||= local_assigns[:mode] = local_assigns.fetch(:mode, 'full') %>
I think a better option that allows for multiple default variables:
<% options = local_assigns.reverse_merge(:include_css => true, :include_js => true) %>
<%= include_stylesheets :national_header_css if options[:include_css] %>
<%= include_javascripts :national_header_js if options[:include_js] %>
Ruby 2.5
Erb
It's possible, but you must to declare your default values in the scope.
VARIABLE the word for replacement.
# index.html.erb
...
<%= render 'some_content', VARIABLE: false %>
...
# _some_content.html.erb
...
<% VARIABLE = true if local_assigns[:VARIABLE].nil? %>
<% if VARIABLE %>
<h1>Do you see me?</h1>
<% end %>
...
More intuitive and compact:
<% some_local = default_value unless local_assigns[:some_local] %>
If you do not want to pass local variable to partial each time you call it you do this:
<% local_param = defined?(local_param) ? local_param : nil %>
This way you avoid undefined variable error. This will allow you to call your partial with/without local variables.
A helper can be created to look like this:
somearg = opt(:somearg) { :defaultvalue }
Implemented like:
module OptHelper
def opt(name, &block)
was_assigned, value = eval(
"[ local_assigns.has_key?(:#{name}), local_assigns[:#{name}] ]",
block.binding)
if was_assigned
value
else
yield
end
end
end
See my blog for details on how and why.
Note that this solution does allow you to pass nil or false as the value without it being overridden.

Resources