Sorting by month in an array Ruby on Rails - ruby-on-rails

I need an array that gives me #idea.id sorted by #idea.created_at.month
For example:
[[1,2,3], [4,5,6], [7,8,9], [], [], [], [], [], [], [], [], []]
where ids 1, 2, and 3 have #idea.created_at.month = 1 and so on through month = 12.
#ideas_by_month = Array.new(12){Array.new}
#ideas.each do |idea|
month = idea.created_at.month
#ideas_by_month[month-1] << idea.id
end
By the example, I'd need #ideas_by_month[0] to give me ids 1, 2, 3.
This currently adds all of the ideas into one slot [], and isn't sorting properly. How can I change it to make my array look like the example?

Array.new(12,[]) gives you 12 references to the same array. Array.new(12){Array.new} creates 12 different arrays.

The issue is not in your << call, but in your creation of the #ideas_by_month Array. From the Ruby API...
Array.new(3, true) #=> [true, true, true]
Note that the second argument populates the array with references to
the same object. Therefore, it is only recommended in cases when you
need to instantiate arrays with natively immutable objects such as
Symbols, numbers, true or false.
So, when you're pushing into any of the nested Arrays, it's all referencing the same space in memory. Which is why it appears that every id is getting pushed into every nested Array.
Instead declare your Array with a block:
#ideas_by_month = Array.new(12) { Array.new }
...which would look like this fully implemented as a class method:
idea.rb
class Idea < ActiveRecord::Base
...
def self.ideas_by_month
#ideas_by_month = Array.new(12){Array.new}
Idea.all.each do |idea|
month = idea.created_at.month
#ideas_by_month[month-1] << idea.id
end
return #ideas_by_month
end
...
end

Related

Fastest way to remove a key from array and make it last

I have to remove the 'Other' Category from the array, which is originally sorted alphabetically, and just make it the last index. I created this little helper but believe there could be a faster way of accomplishing this.
The array is something like this [#<Category id: 17, title: "Books">, #<Category id: 18, title: "Children's Clothing">,
Here is what I've done. It works. Although, I was wonder if theres a more efficient way.
<%
#options = []
#other_option = []
#free_item_options.each do |category|
if category.title.downcase == "other"
#other_option << category
else
#options << category
end
end
#options << #other_option[0]
%>
In cases like this, I usually reach for multi-parameter sorting.
#free_item_options.sort_by do |option|
[
option.title.casecmp?('other') ? 1 : 0,
option.title,
]
end
"Other" category will have 1 and will sort last. Everything else will have 0 and will sort between themselves by ascending title.
Another approach is to just use SQL.
#free_item_options = Category.select("categories.*, (LOWER(title) = 'other') as is_other").order('is_other', :title).to_a
There is Enumerable#partition which is designed to split a collection up in two partitions.
#other_option, #options = #free_item_options.partition { |category| category.title.casecmp?('other') }
#options.concat(#other_options)
If you are certain there is a maximum of one "other" category (which seems to be the case based upon #options << #other_option[0]). You could also use find_index in combination with delete_at and <<. find_index stops iterating upon the first match.
index = #free_item_options.find_index { |category| category.title.casecmp?('other') }
#free_item_options << #free_item_options.delete_at(index) if index
Keep in mind the above does mutate #free_item_options.

Array of Hashes push into another Array

I've an array contains hashes, I want to filter few parameters from the hash and insert the filtered data in another array but am not succeed below is the sample data I've used
a = Array.new
a = [
{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"},
{"name"=>"name2", "age"=>"26", "sex"=> "M", "city"=>"Banglore"}
]
line_item = Array.new
hash_data = {}
a.each do |datas|
hash_data[:name] = datas["name"]
hash_data[:age] = datas["age"]
line_item << hash_data
end
I am getting this result:
[
{:name=>"name2", :age=>"26"},
{:name=>"name2", :age=>"26"}
]
But am expecting this:
[
{:name=>"hello", :age=>"12"},
{:name=>"name2", :age=>"26"}
]
Somebody please help to sort out this, Thanks in advance
Defining the hash outside the loop means that you keep adding the same hash object again (while overwriting its previous values). Instead, create a fresh hash within the loop:
line_items = []
a.each do |datas|
hash_data = {}
hash_data[:name] = datas["name"]
hash_data[:age] = datas["age"]
line_items << hash_data
end
The code looks a bit unidiomatic. Let's refactor it.
We can set the keys right within the hash literal:
line_items = []
a.each do |datas|
hash_data = { name: datas["name"], age: datas["age"] }
line_items << hash_data
end
We can get rid of the hash_data variable:
line_items = []
a.each do |datas|
line_items << { name: datas["name"], age: datas["age"] }
end
And we can use map to directly transform the array:
line_items = a.map { |h| { name: h["name"], age: h["age"] } }
#=> [{:name=>"hello", :age=>"12"}, {:name=>"name2", :age=>"26"}]
You can get the expected result with a combination of map and slice
a = [
{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"},
{"name"=>"name2", "age"=>"26", "sex"=> "M", "city"=>"Banglore"}
]
a.map{ |e| e.slice("name", "age") }
#=> [{"name"=>"hello", "age"=>"12"}, {"name"=>"name2", "age"=>"26"}]
map: Returns Array containing the values returned by block
slice: Returns Hash including only the specified keys
In your loop you are essentially populating line_item with hash_data twice. This is the same object however. You can remedy this by using .dup.
a.each do |datas|
hash_data[:name]=datas["name"]
hash_data[:age]=datas["age"]
line_item << hash_data.dup # <- here
end
irb(main):044:0> line_item
=> [{:name=>"hello", :age=>"12"}, {:name=>"name2", :age=>"26"}]
Edit: I prefer rado's suggestion of moving your definition of hash_data inside the loop over using .dup. It solves the problem more than treating the symptom.
I think a lot of people are over complicating this.
You can achieve this using the following:
a.map { |hash| hash.select { |key, _value| key == 'name' || key == 'age' } }
If you want to return an array, you should nearly always be using map, and select simply selects the key - value pairs that match the criteria.
If you're set on having symbols as the keys, you can call symbolize_keys on the result.
I'll expand the code so it's a little more readable, but the one liner above works perfectly:
a.map do |hash|
hash.select do |key, _value|
key == 'name' || key == 'age'
end
end
On the first line hash_data[:name]=datas["name"] you are setting the key of the hash. That's why when the loop iterate again, it is overriding the value and after that push the new result to the hash.
One solution with reusing this code is just to put the hash_data = {} on the first line of your loop. This way you will have a brand new hash to work with on every iteration.
Also I would recommend you to read the docs about the Hash module. You will find more useful methods there.
If you want for all keys you can do this
array = [{"name"=>"hello", "age"=>"12", "sex"=> "M", "city"=>"Chennai"}, {"name"=>"name2", "age"=>"26""sex"=> "M", "city"=>"Banglore"}]
new_array = array.map{|b| b.inject({}){|array_obj,(k,v)| array_obj[k.to_sym] = v; array_obj}}
Ref: inject
Happy Coding

Updating Ruby Hash Values with Array Values

I've created the following hash keys with values parsed from PDF into array:
columns = ["Sen", "P-Hire#", "Emp#", "DOH", "Last", "First"]
h = Hash[columns.map.with_index.to_h]
=> {"Sen"=>0, "P-Hire#"=>1, "Emp#"=>2, "DOH"=>3, "Last"=>4, "First"=>5}
Now I want to update the value of each key with 6 equivalent values from another parsed data array:
rows = list.text.scan(/^.+/)
row = rows[0].tr(',', '')
#data = row.split
=> ["2", "6", "239", "05/05/67", "Harp", "Erin"]
I can iterate over #data in the view and it will list each of the 6 values. When I try to do the same in the controller it sets the same value to each key:
data.each do |e|
h.update(h){|key,v1| (e) }
end
=>
{"Sen"=>"Harper", "P-Hire#"=>"Harper", "Emp#"=>"Harper", "DOH"=>"Harper", "Last"=>"Harper", "First"=>"Harper"
So it's setting the value of each key to the last value of the looped array...
I would just do:
h.keys.zip(#data).to_h
If the only purpose of h is as an interim step getting to the result, you can dispense with it and do:
columns.zip(#data).to_h
There are several ways to solve this problem but a more direct and straight forward way would be:
columns = ["Sen", "P-Hire#", "Emp#", "DOH", "Last", "First"]
...
#data = row.split
h = Hash.new
columns.each_with_index do |column, index|
h[column] = #data[index]
end
Another way:
h.each do |key, index|
h[key] = #data[index]
end
Like I said, there are several ways of solving the issue and the best is always going to depend on what you're trying to achieve.

Recursive/Tree like strong parameters?

Is there a way of specifying arbitrarily deep strong parameters for tree structures in Rails 4? For example, how would I specify something as follows:
{
"node": {
"name": "parent",
"nodes": [
{ "name": "child1", "nodes": []},
{ "name": "child2", "nodes": [
{"name": "grandchild", "nodes": []}
]}
]
}
}
To allow each node to have a name attribute, and a nodes attribute?
There may be a cleaner solution for solving this but this is my current work around. The general idea is to count how deep my nesting goes and then auto generate the correct nested hash based on that number. So to follow your example:
def count_levels(node_params)
if !node_params[:nodes].nil?
max = 0
node_params[:node_parameters].each do |child_params|
count = count_levels(child_params[1])
max = count if count > max
end
return max + 1
else
return 0
end
end
def node_params
node_attributes_base = [:id, :name]
nodes = []
(1..count_levels(params[:node])).each do |val|
nodes = node_attributes_base + [node_attributes: nodes]
end
params.require(:node).permit(:id, :name, node_attributes: nodes)
end
(The above example can be cleaned up more since it's based on my code where the top level did not have the same parameters. I left it as I had it since it worked on my system.)
You can solve by depending on the fact that number of allowed level can be more than the levels you actually need, so you can count the occurrence of the recursive key nodes key in your hash and use this count as number of levels.
Note that this count will be more than the levels you actually need, but it's simpler than recursively count number of levels in the hash
So in your controller you can the following:
# your_controller.rb
# include RecursiveParametersBuilder
recursive_nodes_attr = build_recursive_params(
recursive_key: 'nodes',
parameters: params,
permitted_attributes: [:name]
)
params.require(:model_name).permit(:name, nodes: recursive_nodes_attr)
And have the actual strong parameters building code can be like the following
# helper module
module RecursiveParametersBuilder
# recursive_path = [:post]
# recursive_key = :comment_attributes
# recursive_node_permitted_params = [:id, :_destroy, :parameterized_type, :parameterized_id, :name, :value, :is_encrypted, :base_param_id, :parent_param_id]
#
def build_recursive_params(recursive_key:, parameters:, permitted_attributes:)
template = { recursive_key => permitted_attributes }
nested_permit_list = template.deep_dup
current_node = nested_permit_list[recursive_key]
nested_count = parameters.to_s.scan(/#{recursive_key}/).count
(1..nested_count).each do |i|
new_element = template.deep_dup
current_node << new_element
current_node = new_element[recursive_key]
end
nested_permit_list
end
end
This is not possible with strong parameters. You should use plain ruby for that, i.e converting your params to a hash with to_hash and validating the format yourself.

There has got to be a cleaner way to do this

I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today

Resources