How to render boolean data attributes with Haml? - ruby-on-rails

UPDATE: Haml, indeed, already does this automatically! However, I had a hash inside of link_to helper, not a Haml tag and didn't even notice it. Silly me! So the question is pretty much invalid.
Haml makes rendering boolean HTML attributes very easy:
%input{checked: #boolean}
renders a simple <input> if #boolean is falsy, or <input checked> otherwise.
Haml also makes it easy to render data attributes:
%a{data: { is_special: false } }
renders: <a data-is-special="false">.
Is there any way to ask Haml to interpret this custom data-is-special attribute as a boolean one? I would like to not have it present if a falsy value is assigned, and for it to be present if anything truthy is assigned.

Code in brackets is normal ruby code, so if only you could perform this task in ruby, you have yourself a solution. I came up with something like that:
def remove_false(hsh)
Hash[hsh.each_pair.select {|key, value| value}]
end
{data: remove_false(is_special: false)} #=> {:data => {}}
This solution does not play well if you have combined keys that you want to treat specially and normally in single hash.

Related

Conditionally creating a hidden attribute in Slim with Ruby on Rails

I'm trying to get Slim to generate the following HTML if a CONDITION is true:
<div id="start_button" hidden="hidden">
I have tried various methods, like the obvious:
#start_button #{('hidden="hidden"' if CONDITION?)}
= link_to 'Get Started', ...etc...
but that generates:
<div id="start_button">hidden="hidden"
= link_to 'Get Started',..etc...
I know how to do it when setting an attribute like class etc. to something, but because it has got to be all or nothing with "hidden", it's causing me issues.
I've been down quite a few rabbit holes this evening, so any help appreciated!
This is stated in the docs:
Ruby attributes
Write the ruby code directly after the =. If the code contains spaces
you have to wrap the code into parentheses (...). You can also
directly write hashes {...} and arrays [...].
So, anything that's inside () is evaluated as Ruby code, if the statement is evaluated to false, it's somehow "skipped", otherwise it does what's stated in the true branch.
#start_button hidden=('hidden' if true)
<div hidden="hidden" id="start_button"></div>
#start_button hidden=('hidden' if false)
<div id="start_button"></div>

How to tell options_for_select field where to save selected option value?

I'm returning to Rails after almost 5 years away and building out a personal project. In my _form.html.erb file, I'm trying to use a field, but the data never gets saved where I think it will be.
<select>
<%= options_for_select([['black'], ['blue'], ['red']], :selected => :color) %>
</select>
In my index, when I try to use model.color I get nothing returned. I'm sure its something basic I'm not getting, but for some reason Google searches and example code doesn't look like exactly like what I got. I'm not sure what option to pass to tell the form where to save the selected value.
You need to give the select tag a name (e.g. <select name="foo"> populates params[:foo], <select name="foo[bar]"> populates params[:foo][:bar]; alternatively use the select_tag/select form helper methods – see https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag and https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-select).
Be aware: If you haven't used Rails for several years, chances are you don't know about strong parameters which you need to use if you want to do direct assignment (e.g. User.new(params[:user]) doesn't work as it used to in older Rails versions – you need to use strong parameters here). Details: https://edgeguides.rubyonrails.org/action_controller_overview.html#strong-parameters.
You need to assign the input a name in order to do anything meaningful with it - This is not Rails specific. Its universally true for web development. Form data in HTTP is just a bunch of key value pairs and the name attribute sets the key.
In Rails you should use the form builders and input helpers unless you have a very good reason not to - it is after driven by convention over configuration. They will handle assigning the name attribute for you:
<%= form_with(model: #thing) do |f| %>
<%= f.select :color, [['Black','black'], ['Blue','blue'], ['Red', 'red']] %>
# ...
<% end %>
This renders something like:
<select name="things[color]">
<option value="black">Black</option>
# ...
</select>
And would give you the following params hash:
{
thing: {
color: 'black'
}
}
They also provide data bindings between your model and the form so that the inputs are not reset when the user submits an invalid form.
I would really start by reading the rails guides as you have a lot to catch up on.

Rails escapes string inside label tag

I have a label tag, whose content is loaded from a en.yml file.
html.erb
<%=label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person))).html_safe%>
person_name is a helper and outputs a string
persons_helper.rb
def person_name(person)
content_tag(:span,
formatted_name(person.name) || t("helpers.persons.default_name"),
class: 'name').html_safe
end
output string from the helper is passed on t method and concatenated as following
en.yml
name: "Person Name: (%{name})"
I want the output to be like
<label for="person">
Person Name:
<span class='name> John Doe </span>
</label>
but Instead I get
<label for="person">
Person Name:(<span class="name">John Doe</span>)
</label>
I understand that it got to do with html_safe, raw and escaping strings but I just could not get it to work!
Thanks!
Call .html_safe on the method call inside the label_tag. E.g:
<%=label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person).html_safe))%>
It appears that the I18n.t method does not return a SafeBuffer (i.e. an html_safe string). So you should call .html_safe on the output from this method.
<%= label_tag(:name, t(:name, scope:[:helpers, :form], name: person_name(person)).html_safe) %>
Note the .html_safe call has been moved in one parenthesis from where you had it. This can also be made marginally easier to see by using the block form of the label_tag helper.
<%= label_tag(:name) { t("helpers.form.name", name: person_name(person)).html_safe } %>
Note: I also switched to the "helpers.form.name" method of selecting the I18n translation in this example to further increase readability (but this may be just a personal preference -- so use your original style if you prefer!).
Finally, for security purposes -- so that a user's name doesn't come through unescaped -- you should remove the .html_safe from your person_name helper and add a strict html_escape (or sanitize) so that it looks like this:
def person_name(person)
content_tag(:span,
h(formatted_name(person.name)) || t("helpers.persons.default_name"),
class: 'name')
end
In this form, the content_tag will make sure everything is html_safe except for the content. Meaning that the person.name will come through as is and be escaped as needed. However, this is not needed if the formatted_name method returns an already escaped or html_safe name. Basically the point is that you don't want to blindly mark strings as html_safe when they come from user inputted values because you don't know if they contain script tags or what. Hopefully this didn't confuse. :) In general, only mark strings as html_safe when you are 100% sure that they are actually always going to be safe (i.e. they come from within your system and not from user input of any sort).
Rails translations can be automatically marked as html_safe but using a naming convention. If a translation is suffixed with _html then the string is marked as html_safe, likewise keys named html are also marked as html_safe.
# config/locales/en.yml
en:
welcome: <b>welcome!</b>
hello_html: <b>hello!</b>
title:
html: <b>title!</b>
In the above t('hello_html') and t('title.html') will be html_safe strings and will not require a call to raw or .html_safe where as t('welcome') will not be html_safe and will require calling raw or .html_safe to avoid the html in the string being escaped.
See http://guides.rubyonrails.org/i18n.html#using-safe-html-translations

Best way to handle data attributes in Slim

I was evaluating Slim as a replacement for HAML in a personal project, and it doesn't appear to handle HTML5 data attributes as gracefully as HAML. I was hoping someone may have also run into this, or may have known about an option/syntax I haven't yet found in their docs.
HAML allows you to define HTML 5 data attributes simply by using nested hashes like so:
%a{data: {key1: 'val', key2: 'val'}}
resulting in
<a data-key1='val' data-key2='val'></a>
There are multiple ways in Slim
As Hash
Attributes which will be hyphenated if a Hash is given (e.g. data={a:1,b:2} will render as data-a="1" data-b="2")
Use it directly as "mu is too short" mentioned, quite intuitive.
a data-title="help" data-content="foo"
Use Ruby code. I often do this and rarely above.
= link_to 'foo', bar_path, data: {a: 'a', b: 'b'}
Use the splat operator:
h1#section-title*{'data-url'=>'test', 'data-id'=>'test'} = #project.name
.your-class*{data: {first_attribute: 'first value', second_attribute: 'second value'} }
Will produce
<div class="your-class" data-first_attribute="first value" data-second_attribute="second value"></div>
I prefer this kind to fix...
#products.each do |product|
.module data-id=product.id
It is working for me

rails interview question

I got this question in a previous interview and couldnt do it , any idea?
What does this return? Where would it be used?
module ApplicationHelper
def show_flash
flash.map{|key, value| content_tag(:div, value, {:class => key})}
end
end
The 'flash' is a ruby-on-rails convention for storing information generated in one request (say, "invalid username" or "session not found" or "thanks for buying from us" or "cart updated") temporarily for being rendered into the next view from the client.
The flash is a hash-like object.
The .map method on hash-like objects will iterate over all items in the hash; in this case, the .map method is being passed a block that accepts two parameters (which it names key and value, because the key could be used to look up the value from the hash). The block uses the content_tag helper to output new <div> elements with the value from the hash and the CSS selector-class key.
So for a flash like this: {:name => "sars", :food => "pizza"}
It would emit HTML roughly like this: <div class="name">sars</div><div class="food">pizza</div>.
This is a clever little helper method that probably saves a fair bit of typing, but it makes some assumptions: order in the view doesn't matter, all the keys are either in the CSS already or the CSS is prepared to handle unknown class elements in a graceful way. This helper might only be used once in a template, but it'd still be helpful to have as a method that could be dropped into other projects later.
module ApplicationHelper
def show_flash
flash.map{|key, value| content_tag(:div, value, {:class => key})}
end
end

Resources