link_to_unless problem - ruby-on-rails

I have this RoR snippet in a view:
<%= link_to_unless(#posts_pages[:previous].nil?,
"Previous",
blog_with_page_path(:page_num => #posts_pages[:previous])) %>
Here blog_with_page is a named route. The snippet works if #posts_pages[:previous].nil? is false (as expected) and the link is generated correctly. However, when #posts_pages[:previous].nil? is true, instead of simply getting the "Previous" string back, I get an error telling me that the route couldn't be generated using :page_num=>nil. Is this the expected behavior? If the condition is met, the route code shouldn't be evaluated, should it?
Here's the complete error:
blog_with_page_url failed to generate from {:page_num=>nil, :action=>"show", :controller=>"pages"}, expected: {:action=>"show", :controller=>"pages"}, diff: {:page_num=>nil}
I've been looking at the link_to_unless code and I don't understand why I get the error since it should be returning simply the name:
# File actionpack/lib/action_view/helpers/url_helper.rb, line 394
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
if condition
if block_given?
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
else
name
end
else
link_to(name, options, html_options)
end
end
I'm using Rails 2.3.11 and Ruby 1.8.7
Cheers!

Because Ruby is not a lazy language, blog_with_page_path(:page_num => #posts_pages[:previous]) gets evaluated as soon as you call it, regardless of whether the value ever gets used by link_to_unless.

Given the various other answers which explain your problem, why not try this as your solution:
link_to( "Previous", blog_with_page_path(:page_num => #posts_pages[:previous]) ) unless #posts_pages[:previous].nil?
This will evaluate the "unless" condition first, sparing you the bogus link_to when #post_pages[:previous] is nil.
-- EDIT --
As pointed out in the comment, since you need the string back maybe the simplest way is just a ternary:
#posts_pages[:previous].nil? ? "Previous" : link_to( "Previous", blog_with_page_path(:page_num => #posts_pages[:previous]) )

It looks like a bug. This code is not "lazy" so it executes all statements. So you can go three ways:
Patch it
Make simple if .. else
Hack it:
Like this
<%= link_to_unless(#posts_pages[:previous].nil?,
"Previous",
blog_with_page_path(:page_num => #posts_pages[:previous] || 0)) %>
Instead of 0 you can set any number, it will never be setted

Function arguments are always evaluated before the function runs, whether or not they are needed:
ree-1.8.7-2011.03 :005 > def print_unless(condition, thing_to_print)
ree-1.8.7-2011.03 :006?> puts "I started executing"
ree-1.8.7-2011.03 :007?> puts thing_to_print unless condition
ree-1.8.7-2011.03 :008?> end
=> nil
ree-1.8.7-2011.03 :009 > print_unless(true, 1/0)
ZeroDivisionError: divided by 0
from (irb):9:in `/'
from (irb):9
from :0

Related

ruby call method with double splash parameter

I've got a legacy helper method defined as:
def feature(label, style = 'text', inverse: false, **html_options)
called by another helper that calls it with:
def create_feature(level, html_options = {})
label, value = .....
feature(label, value, html_options)
end
where in my instance:
(byebug) label
"In progress"
(byebug) value
:pending
(byebug) html_options
{ "data-value"=>"pending", :class=>"mr-2"}
I call this in the view I'm editing (the only bit of code I'm happy to modify in this instance):
<%= create_feature(level, my_helper(:pending).merge({class: 'mr-2'})) %>
where my_helper generates the data-value: pending attribute for the html element.
the previous version of this code was:
<%= create_feature(level, class: 'mr-2') %>
which worked, I need now to add the hash with the extra attribute from the my_helper but all I get is:
*** ArgumentError Exception: wrong number of arguments (given 3, expected 1..2)
oddly I created a dummy version of the same code, and it works just fine:
def feature(label, style = 'text', inverse: false, **html_options)
pp html_options
end
def create_feature(level, html_options = {})
label, value = ['in progress', :pending]
feature(label, value, html_options)
end
create_feature(12, {hello: 'hello', class: 'mr-2'})
# {:hello=>"hello", :class=>"mr-2"}
Since the helper defines html_options as a "take all" keyword argument, you have to pass it as such:
def create_feature(level, html_options = {})
label, value = .....
feature(label, value, **html_options)
# ^^
# add this
end
The ** operator will convert your hash to keyword arguments.
Figured out the issue, it's not documented, but the hash passed in the splash parameter need to have all symbols as keys. Strings won't do.
So what I had to do is to call the method and use .symbolize_keys in the parameter.
I find this a bit annoying, because you are forced to remember this implementation detail in every call to a method with splash parameters. but this is how it is.

Activerecord array to a variable.

I need to get the contents of an activerecord array into a variable.
<%= select_tag :operators,
options_for_select(#operator_list,
:selected => previous_operators(params[:id], action_name)),
),
{:multiple => true, :size => 11}
%>
previous_operators gets the contents of the operators column (an array)
def previous_operators(id, action)
if action_name != "new" && action_name != "create" # prevent error if a new bedsheet line.
#slitter_bedsheet = SlitterBedsheet.find(id) # grab the current bedsheet line
#previous_operators = Array.new
#previous_operators = #slitter_bedsheet.operators # get the keywords for the current bedsheet line
end
if #previous_operators.present?
operators = Array.new
operators = eval(#previous_operators)
else
# operators = ''
end
return operators
end
The content of operators will look something like
["", "[\"Chris Mendla\"]"]
To summarize, I am trying to get the contents of #slitter_bedsheet.operators into the selected line :selected => previous_operators(params[:id], action_name)).
So far, the selected option is not working in that no items are shown as already selected.
I solved this by using the eval function. I realize that this is a dangerous function but in this case it is an app that is inside our firewall and the input is coming from a select box. There might be another way of doing this but this will work for now.
:selected => eval(previous_operators(params[:id], action_name))

Rails I18n, check if translation exists?

Working on a rails 3 app where I want to check if a translation exists before outputting it, and if it doesn't exist fall back to some static text. I could do something like:
if I18n.t("some_translation.key").to_s.index("translation missing")
But I feel like there should be a better way than that. What if rails in the future changes the "translation missing" to "translation not found". Or what if for some weird reason the text contains "translation missing". Any ideas?
Based on what you've described, this should work:
I18n.t("some_translation.key", :default => "fallback text")
See the documentation for details.
You can also use
I18n.exists?(key, locale)
I18n.exists?('do_i_exist', :en)
:default is not always a solution. Use this for more advanced cases:
helpers/application.rb:
def i18n_set? key
I18n.t key, :raise => true rescue false
end
any ERB template:
<% if i18n_set? "home.#{name}.quote" %>
<div class="quote">
<blockquote><%= t "home.#{name}.quote" %></blockquote>
<cite><%= t "home.#{name}.cite" %></cite>
</div>
<% end %>
What about this ?
I18n.t('some_translation.key', :default => '').empty?
I just think it feels better, more like there is no translation
Caveat: doesn't work if you intentionally have an empty string as translation value.
use :default param:
I18n.t("some_translation.key", :default => 'some text')
sometimes you want to do more things on translations fails
v = "doesnt_exist"
begin
puts I18n.t "langs.#{v}", raise: true
rescue
...
puts "Nooo #{v} has no Translation!"
end
This is a trick but I think it may be useful sometimes...
Assuming you have this in your i18n file:
en:
key:
special_value: "Special value"
default_value: "Default value"
You may do this:
if I18n.t('key').keys.include?(:special_value)
I18n.t('key.special_value')
else
I18n.t('key.default_value')
end
# => "Special value"
if I18n.t('key').keys.include?(:unknown_value)
I18n.t('key.special_value')
else
I18n.t('key.default_value')
end
# => "Default value"
NB: This only works if you're testing anything but a root key since you're looking at the parent.
In fact, what's interesting is what you can get when requesting a parent key...
I18n.t('key')
# => {:special_value=>"Special value", :default_value=>"Default value"}
Rails 4
I was iterating over some urls of jury members. The max amount of urls were 2, and default_lang was "de". Here is the yaml that I used
de:
jury:
urls:
url0: http://www.example.com
name0: example.com
url1:
name1:
en:
jury:
urls:
url0:
name0:
url1:
name1:
Here is how I checked if there was a url given and if it did not exist for another language, it would fallback to the I18n default_lang "de". I used answer of #albandiguer which worked great.
I Hope this helps someone:
<% 2.times do |j| %>
<% if I18n.exists?("jury.urls.url#{j}", "de") &&
I18n.exists?("jury.urls.name#{j}", "de") %>
<%= "<br/>".html_safe if j == 1%>
<a href="<%= t("jury.urls.url#{j}") %>" target="_blank">
<%= t("jury.urls.name#{j}") %>
</a>
<% end %>
<% end %>
Some versions ago there is a easier way i18next documentation > API > t:
You can specify either one key as a String or multiple keys as an Array of String. The first one that resolves will be returned.
Example:
i18next.t ( ['unknown.key', 'my.key' ] ); // It will return value for 'my.key'
Also you can use Contexts. t if not found a key into a context returns the default value.

Allow id attributes with simple_format helper

As a proof of concept here's some console output first:
ruby-1.9.2-p180 :010 > x = "<span id='c_3'>s</span>"
=> "<span id='c_3'>s</span>"
ruby-1.9.2-p180 :011 > helper.simple_format(x)
=> "<p><span>s</span></p>"
The reason for this is that the Rails helper method simple_format call the sanitize method at the very end of it's execution and that method strips out attributes.
I know that sanitize will allow you to specify attributes that should not be stripped. My question is: Is it possible to somehow pass the "white listed" attribute (id in this case) THROUGH simple_format ?
thanks!!
simple_format(x,{}, :sanitize => false)
You cannot pass a white-list, but you can disable sanitization completely by doing
simple_format(x, :sanitize => false)
http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-simple_format

Proper way to write this condition

unless #client.nil?
TestMailer.snap_shot_error("test1","Errors",
{:file_name => File.basename(upload_file),:client_name => #client.client_name})
else
TestMailer.snap_shot_error("test1","Errors",
{:file_name => File.basename(upload_file))
end
def snap_shot_error(to_address,subject,options={})
# code
end
<% if #client_name %>
<%= _("There were problems with file ") + #file_name + _(" for client ") + #client_name %>
<% else %>
<%= _("There were problems with file ") + #file_name %>
<% end %>
For both of these questoin, you can use the ternary operator. It works like this
condition ? value_if_true : value_if_false
This is an expression (a sequence of values and operators that produces another value). It determines whether the condition is true or false, and evaluates to the first value (after the ? and before the :) if the condition is true, and the second value (after the :) if the condition is false.
So, for the first code example you posted, you can do this:
TestMailer.snap_shot_error("test1", "Errors",
:file_name => File.basename(upload_file),
:client_name => #client ? #client.client_name : nil)
[Note that I've remove the curly braces around the options -- in Ruby there are not necessary for the final options hash, and it is idiomatic to leave them off]
Or if for some reason you don't even want a nil :client_name in the hash, you can use the ternary operator and a merge:
TestMailer.snap_shot_error("test1", "Errors",
{:file_name => File.basename(upload_file)}.merge(
#client ? { :client_name => #client.client_name } : {}))
For the view, you can also use the ternary operator:
<%= _("There were problems with file ") + #file_name +
(#client_name ? _(" for client ") + #client_name : '' ) %>
And now that I see what you are doing with #client_name, I don't see why you said you require that it not even be in the hash. The first code example I posted, where it passes ":client_name => #client.client_name" if there is a client and passes ":client_name => nil" if there is not a client, should work just fine. There's no reason to not pass :client_name instead of just passed a nil :client_name.

Resources