Set first group of group_by? - ruby-on-rails

Good day, I was wondering, is it possible if i make a selection with group_by like so
#p = Performance.includes(place: [:category]).
order("places.title ASC").
group_by{|p| p.place.category}
so if i want a specific category to be the first, what do i do?
EDIT 1
in view a parse through the results by #p.each do |p|

The return value of group_by is just a normal hash, so you can apply sort_by on it to place your desired category first:
group_by { |p| p.place.category }.sort_by { |k,v| (k=="category name") ? "" : k }
where category name is the name of the category you want to prioritize (the empty string make it come first in the sort results, everything else will just be sorted alphabetically).
This will transform the hash into an array. If you want to keep the data in hash form, wrap the result in Hash[...]:
Hash[group_by { |p| p.place.category }.sort_by { |k,v| (k=="category name") ? "" : k }]
See also this article on sorting hashes: http://www.rubyinside.com/how-to/ruby-sort-hash
UPDATE:
A slightly less processor-intensive alternative to sorting:
grouped = group_by { |p| p.place.category }
Hash[*grouped.assoc("category name")].merge(grouped.except("category name"))
There might be a simpler way to do this, but basically this prepends the key and value for "category name" to the head of the hash.

Although I think shioyama's answer might help you, I doubt you really need the sort process. As shio correctly states, the return value of your sort_by is a hash. So why dont you just access the value, which you want as the first value, simply by using it as hash-key?

Related

Iterate through parameters array and ActiveRecord:Relation

I have a controller which needs to implement bulk update. (However, it needs to set specific values to each object vs the same value for all objects).
Here is the array which the controller will get
[
{
"task_id": 1,
"some_property": "value1"
},
{
"task_id": 2,
"some_property": "value2"
},
]
I need to find all tasks and for each task update the property to a provided value.
The obvious solution is
task_ids = params[::_json].map { |task| task[:task_id] }
tasks = Task.where(id: task_ids)
tasks.each do |task|
params[::_json].each do |task_from_params| do
if task.id == task_form_params[:task_id]
task.some_property = task_form_params[:some_property]
task.save!
end
end
end
The thing which I don't like (big time) is the code where we do N^2 comparisons.
I am pretty sure there should be a better way to do in Rails. I am looking for something more concise which doesn't require N^2 comparisons.
Option 1: ActiveRecord::Relation#update_all
If you don't care about validations and callbacks then you can simply use:
params.each do |param|
Task.where(id: param.fetch('task_id')).
update_all(some_property: param.fetch('some_property')
end
This will iterate over N items in params and issue N UPDATEs and no SELECTs.
Option 2: Convert to a hash mapping ID to property
If you do care about validations or callbacks then you can convert your input array to a hash first:
# Convert params to a hash. Iterate over N items.
id_to_property = params.map do |param|
[param.fetch('task_id'), param.fetch('some_property')]
end.to_h
# Load corresponding tasks. Iterate over N keys.
Task.where(id: id_to_property.keys).find_each do |task|
# Look up the property value - O(1).
task.update!(some_property: id_to_property[task.id])
end

How to create a grouped select from a flat hash of values?

I am pulling a flat hash of parameters from an external API into my Rails app. I need to present these parameters to the user in a select field.
The hash is formatted as follows:
[["Name", "ID", "Category"]]
I'd like to present this as a grouped select. But I believe the grouped_select tag takes a hierarchical hash, not a flat hash?
Is there an easy way to restructure this hash into a structured hash? i.e.,
['CategoryA', [['Name-A1','ID-A1'],['Name-A2', 'ID-A2']]
For reference, I'm currently handling the hash in my controller like this
#hash = session["hash"].map { |h| [ h["name"], h["id"], h["category"] ] }
and in the view
<%= f.select :hash_id, #hash %>
This gives me an unordered list of options. How can I group on h["category"]?
Thanks for any pointers.
EDIT
By the way, I;ve already tried group_by as it seemed the obvious way to go. But I'm getting can't convert String into Integer. Perhaps I'm not using it correctly?
hash.group_by {|h| h["category"] }.map { |h| [ h["name"], h["id"], h["category"] ] }
hash.group_by {|h| h["category"] }.map { |h| [ h[0], h[1].map{|e| [e["name"],e["id"]]} ] }

Display collection value using their attributes name

I want to display value of collection by passing their respective attribute name.
#mandates is the result of an active-record query.
#tabattributes contains array of attribute names previously selected by users.
The code below show field attributes but I want the value of these field instead.
I've tried several syntaxes but errors occurs each time.
How can I modify my code to do that?
#mandates.map do |f|
#tabattributes.each { |att| " #{att} "}
end
If #mandates is a result set that contains models with attributes a, b, and c and #tabattributes is the array %w{a b} (i.e. you want to extract a and b from each element of #mandates) then:
a = #mandates.map { |m| m.attributes.slice(*#tabattributes) }
will give you an array of hashes with keys 'a' and 'b'. For example:
#tabattributes = %w{id created_at}
slices = #mandates.map { |m| m.attributes.slice(*#tabattributes) }
# slices is now like [ { 'id' => ..., 'created_at' => ... }, ... ]
If you only want the values and don't care about the keys then perhaps this will work for you:
#mandates.map { |m| m.attributes.slice(*#tabattributes).values }
That would give you an array-of-arrays. The first array-of-hashes would probably be easier to work with though.
If you can get at #mandates before accessing the database then you could slice out just the columns you're interested inside the database with something like this:
#mandates = Mandate.select(#tabattributes)
slices = #mandates.map(&:attributes)
If I understand you right, you have an array of elements, and you want to have an array containing the name of each element, is that it ? If yes, then array.map {|elem| elem.name} should do it. There is a shorter form (array.map(&:name)) which does the same, if you're interested in how this is working, I can detail.

how to work with array of hashes

[[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
is the array
how can I get the value corresponding to particular value.
say High returns 5 in this.
or how to convert this array of hashes to an array so that searching becomes easy.
I tried:
find_all { |v| v['name'] == "Low" }
but it says:
cant convert String to Integer
please provide some guidance
How about making a single hash out of it for efficient querying?
arr.flatten.reduce(:merge)
#=> {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
If you have some code like:
array = [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]]
Then turn it into an ruby hash:
hash = array.inject({}) {|h, e| h.merge(e.first) }
# => {"Postponed"=>10, "Low"=>3, "Medium"=>4, "High"=>5}
So you can find 'Low' value easily :
hash['Low']
# => 3
EDIT: The answer of Mark Thomas is pretty great, and shorter than the inject since it does the same thing. He wrote it before I answered. Nice ;)
In the general case, the hashes won't be unique, so you need to filter rather than pick one via indexing. For example, let's say you have this:
arr = [[{:apple => 'abc'}], [{:banana => 'def'}], [{:coconut => 'ghi'}]]
# => [[{:apple=>"abc"}], [{:banana=>"def"}], [{:coconut=>"ghi"}]]
Now let's suppose you want to get the value corresponding to any hash with a :coconut key. Then just use:
arr.flatten.map { |h| h[:coconut] }.compact
# => ["ghi"]
That gives you the list of answers. In this case there's only one matching key, so there's only one entry in the array. If there were other hashes that had a :coconut key in there, then you'd have something like:
# => ["ghi", "jkl", "mno"]
On the whole, though, that's a very unusual data structure to have. If you control the structure, then you should consider using objects that can return you sensible answers in the manner that you'd like, not hashes.
You could simply call #flatten on the original array. That would give you an array of hashes. What I think you would really want is just one hash.
1.8.7 :006 > [[{"Postponed"=>10}], [{"Low"=>3}], [{"Medium"=>4}], [{"High"=>5}]].flatten
=> [{"Postponed"=>10}, {"Low"=>3}, {"Medium"=>4}, {"High"=>5}]
I would ask, what are you doing to get that original structure? Can that be changed?
How about this?
arr = [
[{"Postponed"=>10}],
[{"Low"=>3}],
[{"Medium"=>4}],
[{"High"=>5}]
]
arr1 = []
arr.each{|a|
arr1.push(a[0])
}
Although I wonder if you really just want to get one hash, which you'd do like so:
myHash = {}
arr.each{|a|
a[0].each{|b, c|
myHash[b] = c
}
}
You would then access it like myHash["Postponed"]

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources