Conditional statement in Rails - ruby-on-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.

Related

How to get Rails 4 if a record has two conditions return true else false?

I'm setting up my vote system, and trying to have a helper model so I can check if a user has voted for a card. I'm new to rails and can't seem to figure this one out.
How do I have the model check votes for a record that has the user_id of the current_user and the card_id?
I'm also trying to limit calling the helper many times for each iteration of _cards.html.erb by setting the voted variable. Not sure how to do this, trying to set the variable is just printing true for every card, even the ones that have no votes.
Setting the variable is not working and neither is the helper, as it is always true.
cards_controller.rb:
def if_voted(card_id)
if Vote.where(:user_id => current_user.id, :card_id => card_id) then
true
else
false
end
end
helper_method :if_voted
_cards.html.erb:
<td>
<%= #voted = if_voted(card.id) %>
<% if #voted == true %>
<span class="green"><center>
<% elsif #voted == false %>
<span class="red"><center>
<% else %>
<span class="gray"><center>
<% end %>
<%= card.up_votes - card.down_votes %>
</center></span>
</td>
With the help of #tadman
cards_controller.rb
def if_voted(card_id)
if Vote.where(:user_id => current_user.id, :card_id => card_id).any? then
#vote = Vote.find_by(:user_id => current_user.id, :card_id => card_id)
return #vote.voted
else
return nil
end
end
helper_method :if_voted
_cards.html.erb
<td>
<% #voted = if_voted(card.id) %>
<% if #voted == true %>
<span class="green"><center>
<% elsif #voted == false %>
<span class="red"><center>
<% else %>
<span class="gray"><center>
<% end %>
<%= card.up_votes - card.down_votes %>
</center></span>
</td>
Thank you
The where method always returns a scope even if that scope does not contain any records. The find_by method uses the same options but returns either the first matching record or nil if none are found.
That's not quite what you want here, though. You don't actually want to retrieve any of the records, but instead just check if they exist. The any? method on a scope is true if one or more records exist, or false otherwise.
You should update your code to look like this:
def if_voted(card_id)
Vote.where(:user_id => current_user.id, :card_id => card_id).any?
end
It's worth noting a few things about your Ruby style:
Using then at the end of an if clause, while supported, is extraneous and generally not done.
Comparing things == true is usually a sign your logic is confused. If you're concerned about something being literal true rather than just logically true, use === true instead. In this case, close enough counts, so if (if_voted(...)) should suffice.
Your method returned either true or false but you had three conditions as if you were expecting a maybe to pop up one day.
Method names like if_voted are a little clumsy, especially if used inside an if. Something like has_voted? is much more in line with Ruby and Rails in general, so you get if (has_voted?(...)) which reads a lot better.
Even better would be to migrate this method into the User class so you can eliminate the helper and end up with if (current_user.has_voted?(card_id)) as a very clear way of expressing your intent.

Issue between Unless .blank? and if

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?)

A blank? version for database fields which has 0 oder NULL?

in my database I have a field which holds foreign keys. Sometimes the values are NULL or 0.
I know the helper blank?. Is there something similar to enable if there is a number set in the field? Because blank doesn't work here.
the code for the view is something like this
<%= #b.author unless #b.author_id.blank? %>
you could write your own helper
def identified? author
author.id.blank? or author.id == 0
end
You could try something like:
<% if #b.author_id == 0 %>
#display something here
<% else %>
#display something else
<% end %>
in your view.

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.

block always returns true

Will always return true and erase the entire array
<% users.delete_if do |user| %>
<% false %>
<% end %>
On the other hand
<%
users.delete_if do |user|
false
end
%>
works and does not delete the array.
Can i somehow use the delete_if statement in my view and still be able to insert html?
Thanks
You shouldn't be modifying data at all in your views -- do this in your controller or model instead. Use views only to reflect the current state of your database, not change it.
If you then use the second version of your code in either of those places, this problem will disappear.
Ignoring the side-effect-in-a-view aspect, here is why the block "returns" a value: there is a generated print statement as part of the ERB template generated code between the "%>" and the "<% end". Any value left behind by this generated code will be used as the final statement / expression in the block, and thus its value.
I think, and I would have to test it if you use "yield" in the first form, it will work.
<% users.delete_if do |user| %>
<% yield false %>
<% end %>
But, as the first poster says, you shouldn't change data from a view.

Resources