Ruby on Rails: Interpreting a form input as an integer - ruby-on-rails

I've got a form that allows the user to put together a hash.
The hashes desired end format would be something like this:
{1 => "a", 2 => "x", 3 => "m"}
I can build up something similar by having lots of inputs that have internal brackets in their names:
<%= hidden_field_tag "article[1]", :value => a %>
However, the end result is that builds a hash where all the keys are strings and not integers:
{"1" => "a", "2" => "x", "3" => "m"}
I'm currently fixing this by generating a new hash in the controller by looping over the input hash, and then assigning that to params. Is there a cleaner, DRYer way to do this?

Your params will always come in with string keys and values. The easiest way to fix this is to either write your own extension to Hash or simply inject as required:
numeric_keys = params['article'].inject({ }) do |h, (k, v)|
h[k.to_i] = v
h
end
Then you have a hash with the keys converted to integer values, as you like.
A simple extension might be:
class Hash
def remap_keys
inject({ }) do |h, (k, v)|
h[yield(k)] = v
h
end
end
end
This is much more generic and can be used along the lines of:
params['article'].remap_keys(&:to_i)

That depends a bit what you want to use it for. Maybe it is easier to just use strings as keys, and do the "conversion" when accessing the array (or not at all)?
It is also possible to build an array using something like
<%= hidden_field_tag "article[]", :value => "x" %>
this will return "article" as an array, and you can access it directly by index. However, there is no way to influence the position - the array will contain all values in order of appearance.
Lastly, you can make your own version of Hash or just modify the keys, as has been explained.

Related

Stuck with send hash with remote true

I was stuck at a point badly..when I was doing with Rails form_for submit request with remote:true with a hidden field containing array of hashes as below:
<%= f.hidden_field :staff_stat_data, :value =>[{a: "a"} , {b: "b"}] %>
then I am getting hash as a string in parameter like:
"{:a=>\"a\"} {:b=>\"b\"}"
Badly stuck with this.
You're not getting a hash, you're getting a string that kind of looks like a hash.
Remember that each parameter is just a string, that's how data is passed between clients and servers. Rails can sometimes receive an array, but only when the parameter names describe an array (e.g, "user_favourites[]").
If you want to pass a single string that represents an array or hash, you can use JSON to encode/parse the data.
In your view, first change the array to its JSON representation like this:
<%= f.hidden_field :staff_stat_data, :value => [{a: "a"} , {b: "b"}].to_json %>
Then in your controller, change it to a hash by parsing the JSON like this:
staff_stat_data = JSON.parse(params[:staff_stat_data])
This will return you an array, where each element is a hash, just like you want.
You can try this out easily in your Rails console.
json = [{a: "a"} , {b: "b"}].to_json # => "[{\"a\":\"a\"},{\"b\":\"b\"}]"
JSON.parse(json) # => [{a: "a"} , {b: "b"}]

Get key names from key value pairs

I'm running Rails 4 on Ruby 2.0
I'm trying to populate a select tag with a key value pair array I have setup in my model. However, I am having trouble figuring out how to grab the key. Here is what I have so far:
Model
class Store
Colors = ['blue', 'green', 'red', 'yellow', 'orange', 'pink', 'purple', 'lime', 'magenta', 'teal']
SearchParams = {'isbn' => 'ISBN', 'intitle' => 'Title', 'inauthor' => 'Author', 'inpublisher' => 'Publisher', 'subject' => 'Subject', 'lccn' => 'LCCN', 'oclc' => 'OCLC'}
end
Controller
def index
#search_params = Store::SearchParams.map { |param| [param, param.key] }
end
note: I am aware that .key does not exist - I added that hoping it would better communicate what I am trying to do.
View
<%= form_tag do %>
<%= select_tag :param_name, #search_params, prompt: 'choose' %>
<% end %>
I would like the value of each <option> to be the key, and for the user to see the value. I hope that makes sense.
You generally use options_for_select to provide the options for select_tag. Conveniently, you can hand a Hash to options_for_select and it will do the right thing:
options_for_select(container, selected = nil)
Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. [...]
The problem is that options_for_select wants the Hash's keys to be what the human sees and the values to be the value attributes for the <option>s. Your Hash is backwards but you can use Hash#invert to flip the keys and values around:
invert → new_hash
Returns a new hash created by using hsh’s values as keys, and the keys as values.
So you could just do this in your controller:
#search_params = Store::SearchParams.invert
and then this in the ERB:
<%= select_tag :param_name, options_for_select(#search_params), prompt: 'choose' %>
I think, this itself will work
def index
#search_params = Store::SearchParams.to_a
//it will return the array you want, but the values will be [[key1,value1],[key2,value2]]
// if you want to reverse itm then Store::SearchParams.to_a.collect{|arr| arr.reverse} will give that
end
That will do it:
def index
#search_params = Store::SearchParams.map { |key, val| [val, key] }
end
UPD: consider also Hash#invert (thanks to #mu is too short)

Explaining hashes in Ruby

I'm reading a book Crafting Rails Applications by Jose Valim and have come across something that I don't understand. I'm wondering if someone can explain the difference in plain English between the three types of hash below.
For example, in what way is the nested hash (as its represented in this example) a nested hash. In other contexts, I understand nested hashes, but don't get it here.
In what way is an "array" a "key" in the second example. To me it looks just like an array with four variables.
In what way is the third example a hash with "hash as key".
Nested hash
#cached[key][prefix][name][partial]
Simple hash with array as key
#cached[[key, prefix, name, partial]]
Simple hash with hash as key
#cached[:key => key, :prefix => prefix, :name => name, :partial => partial]
The nested hash, is well, a nested hash. The example given, #cached[key][prefix][name][partial], is showing you the "path" to a particular value, so in this case the hash might look something like this:
#cache = {
key => {
prefix => {
name => {
partial => "value"
}
}
}
}
For the simple hash with an array as a key, they're using that 4-element array as one of the keys in the hash.
#cache = {
[key, prefix, name, partial] => "value",
another_key => "another value"
}
For the simple hash with hash as a key, they're using that hash (note that the {}'s for the hash are optional, which may cause some confusion) as one of the keys in the hash.
#cache = {
{:key => key, :prefix => prefix, :name => name, :partial => partial} => "value",
another_key => "another value"
}
Hope that helps!
A hash simply associates key objects to value objects. The keys and values can be anything.
If a value object is itself a hash, you could call it a "nested hash" because in some sense it is inside the main hash.
If a key object is an array, then you get a "hash with array as key".
If a key object is itself a hash, then you get a "hash with hash as key".
See amfeng's answer for a good visual representation of these different cases.
You will need to be somewhat familiar with Ruby syntax to identify the different cases when you see them.
For example, to understand #cached[[key, prefix, name, partial]] you need to know that [key, prefix, name, partial] represents an array, so what you have is like #cached[array], which means an array is being used as a key.
When you see something like #cached[key][prefix] you should know that it is equivalent to (#cached[key])[prefix] so the value object (#cached[key]) is some sort of object that responds to the [] method. In this case, it is a nested hash because the author told you so, but if you didn't know that context then it is possible for it to be something else.
When you see something like #cached[:key => key, :prefix => prefix, :name => name, :partial => partial] you should know it equivalent to #cached[{:key => key, :prefix => prefix, :name => name, :partial => partial}], which means we are using as hash as a key.

Ruby - bad output from arrays

I have a form with a lots of these text inputs:
<%= text_field_tag 'name[seq]['+dat.id.to_s+']', dat.seq%>
After send this form I want to save them to database, I try to get the values from inputs in each loop:
unless params[:name].nil?
params[:name][:seq].each_with_index do |sq, i|
puts sq
end
end
But the output in terminal is wrong, for example if I have an input with the values
<%= text_field_tag 'name[seq][25]', 3%>
So I am going to expect the output is 3, but I will get to terminal this:
25
3
Is here something important, what I don't see?
Yes, you are missing something. Within your each_with_index block, sq will be an array and that's why you get that output.
So, what's going on here? Well, your params will contain this:
"name" => { "seq" => { "25" => "3" } }
And that means that params[:name][:seq] is this:
{ "25" => "3" }
Then you apply each_with_index to that to iterate through the Hash. If you do it like this:
params[:name][:seq].each_with_index do |(k,v), i|
puts "-#{k}-#{v}-"
end
you'll see what's going on.
If you just want the 3 then you can iterate over params[:name][:seq] as above and just look at v inside the block or, if you know what the '25' is some other way, you could just go straight there:
three = params[:name][:seq]['25']

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