Get key names from key value pairs - ruby-on-rails

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)

Related

Rails passing in variables into view helper methods from form selection

I have a bit of code in my user.rb model like this:
def self.aggregate(articles)
array = []
articles.each do |a|
array << {
:id => a.nid,
:views => a.daily_view_metrics.sum_views(a.nid),
:date => a.daily_view_metrics.latest_date(a.nid),
:title => a.daily_view_metrics.latest_title(a.nid),
:visits => a.daily_view_metrics.sum_visits(a.nid)
}
end
return array
end
In my user_controller I pass into the show method #metrics = User.aggregate(#articles) (#articles being simply a subset of articles for that user)
Now in my view (user#show) i call #metrics.each do |m| and then output all the different things in a table. Now according to this video it seems that the link_to method with a url parameter seems to be the best way to have users dynamically switch what they want to sort against.
How can I input that url parameter to sort the array? I tried calling #metrics.sort_by{|h| h[params[:sort]]}.each do |m| with :sort being the url parameter from my links (i.e. the views table header link click passes :sort => ":views" in. Essentially I am trying to do this sort_by{|h| h[:views]} since that works fine for sorting the array. However nothing happens. The array isn't sorted.
EDIT:
I solved it by making the aggregate method pass the key in as a string (i.e. "id" as opposed to :id). then the url params works beautifully.
<%= link_to "Views", :sort => "views"%> now sorts it by views in ascending order.
To order in descending mode you can negate - the element that you are using to sort by.
Ordering by ascending and then do revert to your collection is inefficient.
For instance
$> [{a: 'a1', b: 1}, {a: 'a2', b: 2}].sort_by{ |h| -h[:b] }
# => [{:a=>"a2", :b=>2}, {:a=>"a1", :b=>1}]
$> [{a: 'a1', b: 1}, {a: 'a2', b: 2}].sort_by{ |h| h[:b] }
# => [{:a=>"a1", :b=>1}, {:a=>"a2", :b=>2}]
In the form of your view, you will have something like this (a RadioButton e.g but it could be a Select or whatever you prefer):
<%= radio_button_tag 'radio_order', 'ascending', true %> Ascending
<%= radio_button_tag 'radio_order', 'descending' %> Descending
<%= submit_tag "Order" %>
Then in your helper get the value using params[:radio_order]:
aggregate('views', params[:radio_order])

Multiple identical collection_select tags in a form in Rails

I have multiple identical collection selects inside a single form. I prefer this over a multiple select list for aesthetic and UX reasons. I have to use a terrible kludge to make everything work right, and I'm wondering if there is a more elegant way to do this:
From the view:
<% 3.times do |i| %>
<%= collection_select("selected_item_" + i.to_s.to_s, :name, #items, :name, :name, { :include_blank => true }, { id: "selected_item_" + i.to_s }) %>
<% end %>
From the controller:
ItemContainer = Struct.new(:name)
3.times do |i|
param = ('selected_item_' + i.to_s).to_sym
instance_variable = '#' + param_name
if params[param] && !params[param].empty?
#selected_items << params[param][:name]
instance_variable_set(instance_variable, ItemContainer.new(params[param][:name]))
end
end
#selected_channels.each.... # do what I need to with these selections
Most of these gymnastics are needed to ensure that the item is still selected if the page is refreshed. If there were some way to force collection select to use arrays, that would be the answer, but I couldn't make that work.
If I understang right you're looking for selegt_tag method (docs: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag)
You can write something like this
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
and it youd output two selects for people, which would be sent as an array on submit.
if you use the [] naming in your collection_select calls then the params will send over the data as an array
I am a bit confused as to the usage of collection_select here as it doesn't seem like you are using a model object? this example using select_tag - might be able to come up with something more appropriate to your issue if the model structures were known
# run this in the loop
# set selected_value to appropriate value if needed to pre-populate the form
<%= select_tag('name[]',
options_from_collection_for_select(#items, 'name', 'name', selected_value),
{ include_blank: true }
)
%>
in controller update/create action
# this works because the select tag 'name' is named with [] suffix
# but you have to ensure it is set to empty array if none are passed, usually only issue with checkboxes
names = params[:name] || []
names.each do |name|
puts name
end
side note: you can use string interpolation with ruby double quotes in places of + for string concatenation
<%= collection_select("selected_item_#{i}",
:name,
#items,
:name,
:name,
{ include_blank: true },
{ id: "selected_item_#{i}" }
)
%>
see also: http://apidock.com/rails/v3.2.13/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select

Ruby on Rails: Interpreting a form input as an integer

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.

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.

Rails - Creating a select tag from a object hash

I need to create a select box from the values available in a Hash.
For instance, I have a 'thing' and the 'thing' has a variety of status fields:
1 => 'State A'
2 => 'State B'
available via a method on thing.
How can I build a select tag from this?
Just as Schrockwell has said:
Hash.each |a| returns an array of the form a = [key, value], so for the hash #status_fields you can write:
<%= collection_select('thing', 'status', #status_fields, :first, :last) %>
Alternatively, if you'd like the key to show up in the select list and the value point to the select list value, then:
<%= collection_select('thing', 'status', #status_fields, :last, :first) %>
This will select the option given by thing.status or nothing if nil is returned
If you want to just create any selection not tied to an object use
<%= select_tag('name', options_from_collection_for_select(#status_fields, :first, :last, '2')) %>
where '2' is the index of the desired selection
PS: I don't have enough reputation to just amend the original post or comment on it
you could do something like
select "foo", "bar", #hash_object
or
select "foo", "bar", #hash_object.map { |h| [h.key, h.value] }
I'd probably invert your hash first to make the key point to the value
The select helper method will accept a hash in the form { text_displayed_in_select => select_value }, so you'll probably want to invert that hash.
Hash.each |a| returns an array of the form a = [key, value], so for the hash #status_fields you can write:
<%= collection_select('thing', 'status', #status_fields, :first, :last) %>
Alternatively, if you'd like the key to show up in the select list and the value point to the select list value, then:
<%= collection_select('thing', 'status', #status_fields, :last, :first) %>

Resources