ruby call method with double splash parameter - ruby-on-rails

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.

Related

Questions on method return values and arguments after do

sorry for this noob question I am just not able to understand ruby/rails grammer,
In rails 2.x, I read the following code,
def localized_input_form_for(record_or_name_or_array, *args, &proc)
options = args.extract_options!
args << options.merge(:builder=>LocalizedFormBuilder)
concat('<div class="inputbox">')
form_for(record_or_name_or_array, *args, &proc)
concat('</div>')
end
What does the above function return? Shouldn't it be the return value of the last line statement concat('</div>')?
In the views, I have,
<% localized_input_form_for(#customer) do |f| %>
What is the f in the above code, is it the same f as form_for(xx) do |f|?
The following code works without problem,
<%= f.text_field :name, :required => true, :size => 30,:class =>'ime_on' %>
In rails 4, I made the following modification,
def localized_input_form_for(record_or_name_or_array, *args, &proc)
options = args.extract_options!
args << options.merge(:builder=>LocalizedFormBuilder)
concat('<div class="inputbox">'.html_safe)
concat(form_for(record_or_name_or_array, *args, &proc))
concat('</div>'.html_safe)
end
Without adding concat out of form_for, and without adding html_safe, the original code just doesnt work.
Now, everything still works, the
<% localized_input_form_for(#customer) do |f| %>
works without problem, the form is shown exactly as before. So what is the return value of this function now? and what is f above?
The only difference is, the original options in
<%= f.text_field :name, :required => true, :size => 30,:class =>'ime_on' %>
which are, required: true, size: 30, and class:'ime_on' don't show in the final html!
It generates the following,
<input type="text" value="abc" name="customer[name]" id="customer_name">
without size, class etc options. The html generated by rails 2.x do have these options showing up.
I am just confused about the difference. And I also don't understand why the original rails 2.x and rails 4 both worked (the |f| reflects the form generated by form_for, so f.text_field will get the right value from database).
Yes, your method will return the last line. In your case this is concat("</div>") which evaluates to just "</div>".
The problem is, that concat is not acting as you expect, because it's not occurring within a text buffer and so there's nothing to "concat" to.
To fix this, wrap your helper in a capture block like so:
def some_html
capture do
# ...
concat('<div class="inputbox">')
# ...
concat('</div>>
end
end
More on the capture method: http://apidock.com/rails/ActionView/Helpers/CaptureHelper/capture

'Is not a symbol' error with collection radio buttons ruby

I am working on quiz app, and I can't use collection_radio_buttons for answer choices. I have a tests table.
`Question = test.question`, `choice A = test.answerA` and so on.
<%=
collection_radio_buttons(:test, test[:id], Test.all, :id, [[test.answerA], [test.answerB], [test.answerC], [test.answerD]], {})
%>
But this is giving me an error:
[["A) rasmiy"], ["B) ilmiy"], ["C) so'zlashuv"], ["D) badiiy"]]` is
not a symbol
Why is this happening?
The fifth argument for collection_radio_buttons is the text_method that is to be called on your collection. You are passing an array through this argument instead. The error message ... is not a symbol is telling you that the value that you're passing through that argument is an array, but it's expecting a symbol.
The method definition for collection_radio_buttons is:
collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
You probably want something like:
<%= collection_radio_buttons(:test, test_id, Test.all, :id, :name) %>
... where id and name are callable attributes on instances of whatever comes out of Test.all.
Source: http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormOptionsHelper/collection_radio_buttons

How to extract info from input field in ruby

I'm a frontend + PHP dev, trying to fix [] in a project built in Rails.
[] = Fetch color, show a slightly darker color.
This row:
<%= f.text_field attribute %>
creates an input field with a value that can be translated into a color. I'm at loss as to where to look for how it adds that value. I'm trying to use the value that this input field generates.
this is code from the file select_a_color_input.html.erb inside the app/views/shared folder. Any ideas on where to continue my treasure hunt? :)
update: I found this!
def app_text_field(attribute, args = {})
render_field 'text_field', field_locals(attribute, args)
end
Does that help? ^__^
update:
The form builder
class AppFormBuilder < ActionView::Helpers::FormBuilder
def form_fields(partial = nil , options = {})
partial ||= 'form'
fields = ''
unless options.delete(:without_error_messages)
fields << #template.render('shared/error_messages', :target => Array(#object).last)
end
fields << #template.render(partial, options.merge(:f => self))
end
def app_text_field(attribute, args = {})
render_field 'text_field', field_locals(attribute, args)
end
def app_file_field(attribute, args = {})
render_field 'file_field', field_locals(attribute, args)
end
private
def render_field(name, locals)
#template.render field_path(name), locals
end
def field_locals(attribute, args = {})
help_options = args[:help_options] || {}
field_options = args[:field_options] || {}
html_options = args[:html_options] || {}
{ :f => self, :attribute => attribute, :help_options => help_options, :field_options => field_options, :html_options => html_options, :object => object }
end
def field_path(value)
"shared/app_form/#{value}"
end
end
update:
When I tried to add
<%= content_tag(:p, attribute) %>
It does not give me the values, but instead the id/name of the item, not the colour.
<%= f.text_field attribute %>
This by itself is not very useful to help us gather context. What's the surrounding markup look like? attribute is a ruby variable in this instance. If it were f.text_field :attribute, then :attribute is now a symbol instead of a variable and this would indicate that it maps to the attribute method on X model. This all depends on what your form_for looks like though. I'll give an example:
<%= form_for #user do |f| %>
<%= f.text_field :attribute %>
In this case, we have a form for the User model, and our text_field maps to #user.attribute. The field itself looks something like this:
<input type='text' name='user[attribute]'>
And in the controller's #update or #create action (depending on if this is a new record or an existing record you're editing) the value would be accessible in this fashion:
params[:user][:attribute]
However, it's impossible to say what exactly the params will look like in your particular case. What action is being run? What's the name of the file that this is being loaded from? "app/views/users/new" would indicate the #new action handles this page, and the #create action will handle the form submission.
Things we need to know to fully solve your problem:
Name and relevant code of the controller that's handling this action.
Full view path that this is being rendered from
The rest of the markup starting at form_for and ending at this field attribute
What value does attribute hold? It's a variable, so it must be holding a symbol value or something that will indicate which field is being mapped to this input.

Rails store key value for a select input

I have in my .html.erb:
<label for="form_marital_status">Marital Status:</label>
<%= select("form", "marital_status", marital_status_options, {}, { }) %>
Where marital_status_options is defined in my helper as:
def marital_status_options
%w[Select Single Married Common-Law/Partner Divorced Widowed]
end
Is there a way I can define marital_status_options to have a key value pairing for use in the select?
%w[Select Single Married Common-Law/Partner Divorced Widowed]
This is going to make the option value and text for each option the same. Return an array of arrays if you want the option value and text to be different for each option. The first value in each array is the text value for the option; the second is the option value itself.
def marital_status_options
[["Select", ""], ["Single", "single"], ["Married", "married"], ["Common-Law/Partner", "partners"], ["Divorced", "divorced"], ["Widowed", "widowed"]]
end
This is explained clearly in the documentation.
You should also consider not passing the blank "Select" option from your method as there is a way to do this through the select method itself.
# Helper
def marital_status_options
[["Single", "single"], ["Married", "married"], ["Common-Law/Partner", "partners"], ["Divorced", "divorced"], ["Widowed", "widowed"]]
end
# form
<%= select("form", "marital_status", marital_status_options, {:include_blank => "Select"}) %>

Passing hash as values in hidden_field_tag

I am trying to pass some filters in my params through a form like so:
hidden_field_tag "filters", params[:filters]
For some reason the params get changed in the next page. For example, if params[:filters] used to be...
"filters"=>{"name_like_any"=>["apple"]} [1]
...it gets changed to...
"filters"=>"{\"name_like_any\"=>[\"apple\"]}" [2]
note the extra quotations and backslashes in [2] when compared to [1].
Any ideas? I'm attempting to use this with searchlogic for some filtering, but I need it to persist when I change change objects in forms. I would prefer not to have to store it in session.
My solution was just to re-create each of param with key-value pair:
<% params[:filters].each do |key,value| %>
<%= hidden_field_tag "filters[#{key}]",value %>
<% end %>
You actually want/need to 'serialize' a hash using hidden fields.
Add this to your ApplicationHelper :
def flatten_hash(hash = params, ancestor_names = [])
flat_hash = {}
hash.each do |k, v|
names = Array.new(ancestor_names)
names << k
if v.is_a?(Hash)
flat_hash.merge!(flatten_hash(v, names))
else
key = flat_hash_key(names)
key += "[]" if v.is_a?(Array)
flat_hash[key] = v
end
end
flat_hash
end
def flat_hash_key(names)
names = Array.new(names)
name = names.shift.to_s.dup
names.each do |n|
name << "[#{n}]"
end
name
end
def hash_as_hidden_fields(hash = params)
hidden_fields = []
flatten_hash(hash).each do |name, value|
value = [value] if !value.is_a?(Array)
value.each do |v|
hidden_fields << hidden_field_tag(name, v.to_s, :id => nil)
end
end
hidden_fields.join("\n")
end
Then, in view:
<%= hash_as_hidden_fields(:filter => params[:filter]) %>
This should do the trick, even if you have a multilevel hash/array in your filters.
Solution taken http://marklunds.com/articles/one/314
I just wrote a gem to do this called HashToHiddenFields.
The core of the gem is this code:
def hash_to_hidden_fields(hash)
query_string = Rack::Utils.build_nested_query(hash)
pairs = query_string.split(Rack::Utils::DEFAULT_SEP)
tags = pairs.map do |pair|
key, value = pair.split('=', 2).map { |str| Rack::Utils.unescape(str) }
hidden_field_tag(key, value)
end
tags.join("\n").html_safe
end
Here's how I managed to pass a parameter value through my view - that is, from View A through View B and on to the controller:
In View A (index):
<%= link_to 'LinkName', {:action => "run_script", :id => object.id} %>
In View B (run_script):
<%= form_tag :action => 'index', :id => #object %>
<%= hidden_field_tag(:param_name, params[:id]) %>
In the controller:
Just reference params[:param_name] to make use of the value.
The key transition that wasn't documented anywhere I could find is where {... :id => object.id} from View A is passed on to View B as <%... :id => #object %>, which View B then passes on to the controller as (:param_name, params[:id]) through the hidden_field_tag construct.
I didn't see this documented anywhere but after perusing several posts across several sites including this post (whose syntax provided the key inspiration), the solution finally gelled. I've seen the caveats on hidden fields pertaining to security but have found no other way to do this given my current design, such as it is.
it's because when you convert in HTML with your hidden_field_tag, the backquote is add. After when you received it like a string not a Hash.
The Hash type can't exist in HTML. You have only string. So if you want pass your hash (not recommend by me), you need eval it when you received it. But can be a big security issue on your application.
As a caveat to Vlad's answer, I had to use raw:
<%= raw hash_as_hidden_fields(:filter => params[:filter]) %>
to get it to work in Rails 3.1.1. Essentially, the text being output was being escaped, eg., "<" becoming "&lt".
Assuming the hash is strings, symbols, numbers, and arrays, you can call eval to convert the params string of the hash from the hidden_fields form back into a hash in the controller. Then the backslash escape characters for the quotes added are no longer an issue:
hash = eval(params["hash_string"].to_s)
Credit to the following article for helping identify this simple solution for my case:
How do I convert a String object into a Hash object?
Keep in mind the contents of the params should be cleaned with .require and .permit.

Resources