Is there an inline way to conditionally add an attribute in Ruby? - html

There are a number of times in Rails when I would like to be able to add an attribute to something only if the attribute is of a particular value.
Let me give you an example. Let's say I want to disable a button based on a particular property such as check_if_user_can_be_added:
link_to 'Create account', new_user_path, disabled: (user_can_be_added?)
This all looks fine and well except that disabled happens to be applied in HTML regardless of what value you give it. If you give a button the attribute disabled: false then it will still be disabled.
What I need
# if the button is disabled
link_to 'Create account', new_user_path, disabled: true
# if the button is not disabled
link_to 'Create account', new_user_path
The way that I can see to do it
Getting this means that you need a solution similar to the following which sets up the options hash first and then passes it in subsequently:
options = user_can_be_added? ? {disabled: true} : {}
link_to 'Create account', new_user_path, options
The way I would like to do it...
This doesn't work but trusting in Ruby's beauty I suspect there's something similar out there. This is basically what I'd like to do
link_to 'Create account', new_user_path, ({disabled: true} if user_can_be_added?)
Can I do it, is there perhaps something using the splat operator that gets me there...?

You can just set nil to cause Rails to ignore the attribute:
link_to 'Create account', new_user_path, disabled: (user_can_be_added? ? true : nil)
For this particular case, you can also use || like so:
link_to 'Create account', new_user_path, disabled: (user_can_be_added? || nil)

I'm not familiar with rails, but inline if statements can be achieved as follows in ruby:
(condition ? true : false)
I would assume your code would look like this:
link_to 'Create account', new_user_path, (user_can_be_added? ? disabled: true : nil)
This would essentially pass in nil in place of disable: true if user_can_be_added? resolves to false.
I'm not sure how the link_to function would handle that, though. I guess the reason disabled: false doesn't work, is the link_to function recieves a hash of attributes as the final parameter, which are applied to an html link. Since the disabled attribute in html doesn't need a value, it would just stick in <a href="" disabled> regardless of its value. Someone feel free to correct me though, I haven't used rails.

Years ago I used a Rails plugin for HTML attributes that is still available on Github. That plugin allowed to write html tags like this:
content_tag(:div, :class => { :first => true, :second => false, :third => true }) { ... }
# => <div class="first third">...
Plugins are unsupported by current versions of Rails and plugin only worked for tag helpers, but perhaps this helps you to write a helper yourself.

For many arguments, using the splat will work:
p "Here's 42 or nothing:", *(42 if rand > 0.42)
This won't work for a hash argument though, because it will convert it to an array.
I wouldn't recommend it, but you could use to_h (Ruby 2.0+):
link_to 'Create account', new_user_path, ({disabled: true} if user_can_be_added?).to_h

To me that looks like a great place to use a helper
module AccountHelper
def link_to_create_account disabled = false
if disabled
link_to 'Create account', new_user_path, disabled: true
else
link_to 'Create account', new_user_path
end
end
end
In your ERB it's simply link_to_create_account(user_can_be_added?)
Not everyone likes helpers, but they work for me.

Related

Rails - simple form - adding html options to collection select

I am trying to make sense out of the instructions in this sheet:
http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
My objective is to make the select menu wider than the default width.
I have this form input:
<%= f.input :trl, label: false do %>
<%= f.select :trl_id, Trl.all.map { |t| [t.try(:title), t.try(:id)] },
include_blank: false,
prompt: 'Select one',
input_html: { "width: 200px" }
%>
<% end %>
At the moment, the default width html for the select drop down is super tiny. I'm trying to get the select to accept a html attribute for width.
I have tried about 50 different permutations of the above. I can't find a way that works.
From the instructions in simple form, I'm directed to the instructions in the above link. Those tell me that the structure of the input form field should follow this format:
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
From what I can decipher, I think I need as many comma separated fields as are in this example. So that's 7 in total. That's an assumption because I can't tell if you just skip the ones that I don't want to use or do something else to indicate that there is no entry for that particular field. Following that assumption, I have:
<%= f.input :trl, label: false do %>
<%= f.collection_select(:trl_id, Trl.all.map { |t| [t.try(:title), t.try(:id)] },
include_blank: false,
prompt: 'Select one',
html_options = { width: 200px })
%>
<% end %>
This gives me an error that says:
syntax error, unexpected tIDENTIFIER, expecting '}'
html_options: { width: 200px })
^
I can't find an english language translation of what tIDENTIFIER is or means. Most stack overflow questions referencing this term generally indicate that something is wrong with the syntax.
I'm struggling because I can't understand the apidock instructions in the first place. Do I need to add some more blank fields along the way? If I do, do I just write two commas in a row to indicate a blank field?
There are 7 fields in the api dock example. I think my attempts are missing content for value method and text method. I don't know how to indicate to rails that I don't have any content for them. None of the examples in the API dock indicate blank fields, so I am thinking that less than 7 fields should be acceptable.
I'm also not sure where else to search for the definition of tIDENTIFIER. There may be a clue in that term that I can't access because I can't find the meaning of the term.
Can anyone help?
Late response, but see:
https://api.rubyonrails.org/v7.0.2.3/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select
and
How do I set the HTML options for collection_select in Rails?
Essentially, the collection select definition looks like
collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
as you rightly mentioned in your question, therefore try to apply your pre-defined css classes within the html_options curly braces. If you have nothing to declare for the options {} that come before the html_options (e.g disabled options), leave it empty, e.g.
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {class: "driver_name_css"} %>
I believe your code would look like:
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {style: "width: 200px"} %>
or
<%= form.collection_select :driver_id, Driver.order(:full_name), :id, :full_name, {}, {class: "pre-defined-css-class-with-desired-width"} %>

Adding a class for select tag ruby on rails

I want to add a class for the select tag.Like this.
<select class="phone_no">
I want the ruby equivalent of the above HTML line of code.
<%= select_tag(:id, '<option value="1">Lisbon</option>.') %>
Thanks in advance.
Try this:
<%= select_tag(:id, '<option value="1">Lisbon</option>.',{}, { :class
=> 'phone_no' } ) %>
select helper takes two options hashes:,
(1) one for select - in this case {}
(2) and the second for html options - in this case class
Signature:
select_tag(name, option_tags = nil, options = {}) public
(1) Select options available:
:multiple - If set to true the selection will allow multiple choices.
:disabled - If set to true, the user will not be able to use this input.
:include_blank - If set to true, an empty option will be create
:prompt - Create a prompt option with blank value and the text asking user to select something
(2) Any other key creates standard HTML attributes for the tag
more info here
According the documentation in the select_tag, you shell pass class key-value pair via third option, i.e. shell do as follows. Also you may use html_safe method to pass a proper html as the second argument:
<%= select_tag( :id, <option value="1">Lisbon</option>.'.html_safe, :class => "phone_no" ) %>

How to use Ruby Formhelper with a SELECT element

Using Ruby formhelper,
I want the user to see a popup like "fill this out" when they try to submit the form with any empty fields or unselected selects.
To require a text field, this works:
<%= f.text_field :name, :required => "required" %>
To require a select, I'm trying this but it doesn't work:
<%= f.collection_select :metric, Metric.all, :id, :name, :prompt => true, :required => "required" %>
The select is there and its options are populated correctly in the dropdown. But the user should see a popup if they try to submit without selecting another option besides the default "please select" - it never appears.
You need to use Ruby's select tag combined with Ruby's options_from_collection_for_select. You can set your prompt to true or add a custom prompt and required must be in curly braces and its value set to true. For example:
<%= select('trophy','metric', options_from_collection_for_select(Metric.find(:all), :id, :name),{:prompt => 'Select Metric'},{:required => true})%>
Where:
-trophy is the name of an instance variable or a model object
-metric is is the attribute of that instance variable. This is typically a field/column of the table whose data you're displaying.
-:id is the key
-:name is the result

add comparison feature in rails

i'm having a bit of trouble with adding a certain feature. i'm working on a buy/sell site and i want to be able to compare posts. here's what i have so far:
in the posts view:
<%= button_to "Add to Compare", :action => "addCompare" %>
in the corresponding controller:
##a = Array.new()
def addCompare
##a << Post.id
end
so, all i want to do is add the post's id to the array ##a. when i test this, i click on the "Add to Compare" button and I'm welcomed with this:
Template is missing
Missing template posts/addCompare with {:locale=>[:en, :en], :formats=>[:html], :handlers=>[:rxml, :rjs, :builder, :rhtml, :erb]} in view paths "/home/mja32/470repo/traders/app/views", "/var/lib/gems/1.8/gems/devise-1.4.2/app/views"
So I guess it's trying to redirect to a view. How do I prevent it from doing this? All I want this button to do is to add the post's id to the array and nothing more.
Thanks in advance,
Matt
First of all, storing persistent data in a controller's class variable isn't going to work the way you want it to. There's no guarantee that ##a will be the same array on your next addCompare call; for example, your next addCompare call could be handled by a different process. Also, what happens if two different clients call addCompare? Do you really want to mix their data together in one pile? Probably not. Your first task is to replace ##a with a real per-user persistent store.
If you want to return nothing at all from your controller, just do this at the end of your controller method:
render :nothing => true, :status => :ok
That will tell Rails that something has already been rendered so it doesn't need to try the default rendering action (which is to render the posts/addCompare view) and returns nothing more than a 200 status code to the client.
Once that's in place, you'll probably want to AJAXify your button with :remote => true:
:remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the submit behaviour. By default this behaviour is an ajax submit.
So this:
<%= button_to "Add to Compare", { :action => "addCompare" }, { :remote => true } %>
Note that button_to looks like this:
button_to(name, options = {}, html_options = {})
and that :action is for options but :remote is for html_options so you have to explicitly set up the hashes with {}; you could just wrap the options in braces:
<%= button_to "Add to Compare", { :action => "addCompare" }, :remote => true %>
but I prefer the consistency of wrapping them both by hand.

What's the right way to define an anchor tag in rails?

It's obvious from the documentation (and google) how to generate a link with a segment e.g. podcast/5#comments. You just pass a value for :anchor to link_to.
My concern is about the much simpler task of generating the <a name="comments">Comments</a> tag i.e. the destination of the first link.
I've tried the following, and although they seemed to work, the markup was not what I expected:
link_to "Comments", :name => "comments"
link_to "Comments", :anchor => "comments"
I think I'm missing something obvious. Thanks.
You are getting confused by Ruby's syntactic sugar (which Rails uses profusely). Let me explain this briefly before answering your question.
When a ruby function takes a single parameter that is a hash:
def foo(options)
#options is a hash with parameters inside
end
You can 'forget' to put the parenthesis/brackets, and call it like this:
foo :param => value, :param2 => value
Ruby will fill out the blanks and understand that what you are trying to accomplish is this:
foo({:param => value, :param2 => value})
Now, to your question: link_to takes two optional hashes - one is called options and the other html_options. You can imagine it defined like this (this is an approximation, it is much more complex)
def link_to(name, options, html_options)
...
end
Now, if you invoke it this way:
link_to 'Comments', :name => 'Comments'
Ruby will get a little confused. It will try to "fill out the blanks" for you, but incorrectly:
link_to('Comments', {:name => 'Comments'}, {}) # incorrect
It will think that name => 'Comments' part belongs to options, not to html_options!
You have to help ruby by filling up the blanks yourself. Put all the parenthesis in place and it will behave as expected:
link_to('Comments', {}, {:name => 'Comments'}) # correct
You can actually remove the last set of brackets if you want:
link_to("Comments", {}, :name => "comments") # also correct
In order to use html_options, you must leave the first set of brackets, though. For example, you will need to do this for a link with confirmation message and name:
link_to("Comments", {:confirm => 'Sure?'}, :name => "comments")
Other rails helpers have a similar construction (i.e. form_for, collection_select) so you should learn this technique. In doubt, just add all the parenthesis.
If you want to go through rails, I suggest content_tag (docs).
Example:
content_tag(:a, 'Comments', :name => 'comments')
<%= link_to('new button', action: 'login' , class: "text-center") %>
created an anchor tag for login.html i.g
new button
and for
new button
use
<%= link_to('new button', controller: 'admin',
action: 'login' , class: "text-center") %>