Splitting an array into sub arrays 2 - ruby-on-rails

This question follows on from a similar question I asked previously:
Splitting an array into sub arrays
I have been struggling with this code for a while now. I have an array of data "master" with each element having a lft & rgt value. I wish to break this "master" array in to an array of sub arrays "groups". The desired groupings of these sub arrays is illustrated below.
The trigger to create a new sub array is where the lft value is not between the lft & rgt values of the first elemenet in the array.
My thinking was to:
a) initialise the first array then loop through the remaining elements.
b) Check the element's lft value against the lft & rgt values of the first element in the last sub array.
c) if out side the range then create a new sub array
d) append the element onto the last sub array.
when I try this I recive an error for a unknown method "new"
def display_visiting
groups = []
master = []
master << { id: 1, name: "Fred", lft: 1, rgt: 4 }
master << { id: 4, name: "Sue", lft: 2, rgt: 3 }
master << { id: 2, name: "May", lft: 5, rgt: 12 }
master << { id: 5, name: "Helen", lft: 6, rgt: 7 }
master << { id: 6, name: "Peter", lft: 8, rgt: 9 }
master << { id: 7, name: "Grace", lft: 10, rgt: 11 }
master << { id: 3, name: "Brian", lft: 13, rgt: 18 }
master << { id: 8, name: "Michael", lft: 14, rgt: 15 }
master << { id: 9, name: "Paul", lft: 16, rgt: 17 }
groups[0] = master.shift(1)
master.each do |employee|
if (employee.lft < groups.last.first.lft) or (employee.lft > groups.last.first.rgt)
groups.new
end
groups.last << employee
end
return groups
else
return nil
end

new is a Class method used to create a new instance of a Class. E.g an object. groups is an instance of the Array class which is why it doesn't have a new method. If you're not too familiar with class methods vs instance methods, checkout this article.
So if you wanted to add a new Array to the end of the groups array, you would change this:
groups.new
To this:
groups << Array.new
However, you can use the Array initialization shorthand exactly like how you initialized the master array:
groups << []

Related

Understanding grep within Enumerable module

I greatly appreciate any assistance on my first stack-overflow post!
I was generally confused why I am receiving and empty array every time I try to run my code.
I am creating a method that will filter for a gender of "M" and return its elements.
I am sure there are a multitude of ways to successfully run this code but I was interested in using grep and its functionality. It being a shortcut and all I thought it would be good to learn. Thank you once again.
students = [
{name: 'John', grade: 8, gender: 'M'},
{name: 'Sarah', grade: 12, gender: 'F'},
{name: 'Bob', grade: 16, gender: 'M'},
{name: 'Johnny', grade: 2, gender: 'M'},
{name: 'Ethan', grade: 4, gender: 'M'},
{name: 'Paula', grade: 8, gender: 'F'},
{name: 'Donald', grade: 5, gender: 'M'},
{name: 'Jennifer', grade: 13, gender: 'F'},
{name: 'Courtney', grade: 15, gender: 'F'},
{name: 'Jane', grade: 9, gender: 'F'}
]
def is_male(gender)
gender.grep(/M/) { |gend| gend[:gender] }
end
p is_male(students)
From the docs of Enumerable#grep:
grep(pattern) → array
grep(pattern) { |obj| block } → array
Returns an array of every element in enum for which Pattern === element. If the optional block is supplied, each matching element is passed to it, and the block’s result is stored in the output array.
The important part is this method returns elements that evaluate Pattern === element to true. But /M/ === {name: 'John', grade: 8, gender: 'M'} does not return true, same for all other elements in your array.
Therefore your result set is empty in the first place.
The block – { |gend| gend[:gender] } in your example – is only evaluated when and after there was a pattern match. The block changes the return value of the whole call but not how the pattern matching is done.
Please note the docs for Rexgxp#=== in this context too.
I am creating a method that will filter for a gender of "M" and return its elements.
With the above requirements in mind, your naming seems quite misleading:
def is_male(gender)
# ...
end
The above looks like a method that takes a gender and checks whether it is male. I'd expect something like this:
is_male('M') #=> true
is_male('F') #=> false
And is_male(students) isn't clear either – does it check whether there is a male student in the given array? Or if all of the students are male? Either way, it doesn't sound like filtering.
Let's start by renaming the method and its argument to match your requirements more closely:
def male_students(students)
# ....
end
If you want to use grep you have to provide a pattern which is an object responding to ===. A regular expression won't work, because it operates on strings and our students are hashes (more on that below). You could use a Proc instead which also responds to ===:
def male_students(students)
is_male = ->(student) { student[:gender] == 'M' }
students.grep(is_male)
end
But it's easier to use select:
def male_students(students)
students.select { |student| student[:gender] == 'M' }
end
Another option is to use a custom class for your students because right now, your students are merely hashes:
class Student
attr_accessor :name, :grade, :gender
def initialize(name:, grade:, gender:)
#name = name
#grade = grade
#gender = gender
end
def male?
gender == 'M'
end
def female?
gender == 'F'
end
end
Now, change your array accordingly:
students = [
Student.new(name: 'John', grade: 8, gender: 'M'),
Student.new(name: 'Sarah', grade: 12, gender: 'F'),
Student.new(name: 'Bob', grade: 16, gender: 'M'),
Student.new(name: 'Johnny', grade: 2, gender: 'M'),
Student.new(name: 'Ethan', grade: 4, gender: 'M'),
Student.new(name: 'Paula', grade: 8, gender: 'F'),
Student.new(name: 'Donald', grade: 5, gender: 'M'),
Student.new(name: 'Jennifer', grade: 13, gender: 'F'),
Student.new(name: 'Courtney', grade: 15, gender: 'F'),
Student.new(name: 'Jane', grade: 9, gender: 'F'),
]
And you can use this pretty minimal syntax:
students.select(&:male?)
#=>
# [
# #<Student:0x00007fb18d826ce8 #name="John", #grade=8, #gender="M">,
# #<Student:0x00007fb18d826b58 #name="Bob", #grade=16, #gender="M">,
# #<Student:0x00007fb18d826a90 #name="Johnny", #grade=2, #gender="M">,
# #<Student:0x00007fb18d8269c8 #name="Ethan", #grade=4, #gender="M">,
# #<Student:0x00007fb18d826838 #name="Donald", #grade=5, #gender="M">
# ]
Or any of the other useful methods in Array / Enumerable:
students.any?(&:male?) #=> true
students.all?(&:male?) #=> false
students.count(&:male?) #=> 5

How to update json field's key name in rails using postgresql or activerecord?

I have a table named cart where items is in json type. The value in cart are as follows for 3 examples:
1. Cart(id: 1, name: 'Test', items: [{'desc'=>'First','names'=>'First'}, {'desc'=>'Second','names'=>'Second'}])
2. Cart(id: 2, name: 'Test', items: [{'desc'=>'First','names'=>'First'}, {'desc'=>'Second','names'=>'Second'}])
3. Cart(id: 3, name: 'Test', items: [{'desc'=>'First','name'=>'First'}, {'desc'=>'Second','name'=>'Second'}])
How can I update all the items key to have name key instead of names in rails? Such that it becomes as follow:
1. Cart(id: 1, name: 'Test', items: [{'desc'=>'First','name'=>'First'}, {'desc'=>'Second','name'=>'Second'}])
2. Cart(id: 2, name: 'Test', items: [{'desc'=>'First','name'=>'First'}, {'desc'=>'Second','name'=>'Second'}])
3. Cart(id: 3, name: 'Test', items: [{'desc'=>'First','name'=>'First'}, {'desc'=>'Second','name'=>'Second'}])
I think something like this should work:
ActiveRecord::Base.connection.execute <<-SQL
UPDATE cart t1
SET items = (
SELECT json_agg(
el :: jsonb - 'names' || jsonb_build_object('name', el -> 'names')
) FROM cart t2, jsonb_array_elements(t2.items) as el
WHERE t1.id = t2.id
)
SQL
First get all records:
all_carts = Cart.all
#<ActiveRecord::Relation [#<Cart id: 2, name: "name1", items: "[{"desc":"First","names":"First"},{"desc":"Second"...",
then cycle each record and change the json key:
all_carts.each do |cart|
jitems = JSON.parse(cart.items) # ---> [{"desc"=>"First", "names"=>"First"}, {"desc"=>"Second", "names"=>"Second"}]
jitems.each { |it| it['name'] = it['names']; it.delete('names') } # ---> [{"desc"=>"First", "name"=>"First"}, {"desc"=>"Second", "name"=>"Second"}]
cart.items = jitems.to_json # ---> "[{\"desc\":\"First\",\"name\":\"First\"},{\"desc\":\"Second\",\"name\":\"Second\"}]"
cart.save
end

Sort records by substring (time) of attribute

I have this active record query:
shop = Shop.find(12934)
I then go and get a collection of products from this shop like so
#product_templates = []
shop.products.each do |product|
#product_templates.push(product.template)
end
So now I have an array of active record objects which look like this:
[
#<Product::Template id: 185549, name: "1:30pm", position: 0>,
#<Product::Template id: 185522, name: "11:30am", position: 1>,
#<Product::Template id: 185580, name: "7:00pm", position: 2>,
#<Product::Template id: 185556, name: "10:00am", position: 3>,
]
I want to update the position attribute by ordering each of these Product::Template by the time in the name e.g.
10:00am, 11:30am, 1:30pm, 7:00pm would be the order of the objects so the 10:00am object would get position: 0, 7:00pm would get position:3 etc.
How would I do this? Thanks for your time in advance
try this:
#product_templates.sort { |t1, t2| Time.strptime(t1, "%I:%M%P") <=> Time.strptime(t2, "%I:%M%P") }.each_with_index { |template, index| product.position = index }
What I did there was sorting your template array first, then updating position based on array index.
First of all, your way of filling #product_templates before sort is not a good way.
#product_templates =
shop.products
.map(&:template)
.sort_by{|template| template.name.to_time}

rails array of hashes calculate one column

I have an array and it has many columns and I want to change one value of my one column.
My array is:
[
{
id: 1,
Districts: "Lakhisarai",
Area: 15.87,
Production: 67.77,
Productivity: 4271,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Orange",
Productivity_Colour: "Dark_Green",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
{
id: 29,
Districts: "Begusarai",
Area: 18.53,
Production: 29.35,
Productivity: 1584,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Red",
Productivity_Colour: "Orange",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
...
]
This is my sample array and I want my Productivity to be divided by 100 for that I am using one empty array and pushing these hashes to my array like:
j = []
b.map do |k|
if k.Productivity
u = k.Productivity/100
j.push({id: k.id, Productivity: u })
else
j.push({id: k.id, Productivity: k.Productivity })
end
Is there any simple way where I can generate this kind of array and reflect my changes to to one column. Is there any way where I don't need to push name of column one by one in push method.
I want to generate exact same array with one modification in productivity
let's say your array is e, then:
e.each { |item| item[:Productivity] = item[:Productivity]/100}
Example:
e = [{p: 12, d: 13}, {p:14, d:70}]
e.each { |item| item[:p] = item[:p]/10}
output: [{:p=>1, :d=>13}, {:p=>1, :d=>70}]
You could take help of map method here to create a new array from your original array, but with the mentioned changes.
ary.map do |elem|
h = elem.slice(:id)
h[:productivity] = elem[:Productivity] / 100 if elem[:Productivity]
h
end
=> [{:id=>1, :productivity=>42}, {:id=>29, :productivity=>15}]
Note, Hash#slice returns a new hash with only the key-value pairs for the keys passed in argument e.g. here, it returns { id: 1 } for first element.
Also, we are assigning the calculated productivity to the output only when it is set on original hash. Hence, the if condition there.

Rails translate enum in an array

I am currently working on statistics, so I get an array containing all my data. The problem is that this data contains enums and that I would like to translate them without overwriting the rest.
Here is a given example that contains my array (it contains several hundred) :
#<Infosheet id: 90, date: "2018-04-22 00:00:00", number: 7, way: "home", gender: "man", age: "age1", district: "", intercommunal: "", appointment: true, othertype: "", otherorientation: "", user_id: 3, created_at: "2018-04-22 17:51:16", updated_at: "2018-04-22 17:51:16", typerequest_id: 168, orientation_id: 188, info_number: nil, city_id: 105>
I would like to translate the enums of "way" or "gender" or "age", while retaining the rest of the data, because currently, if I make a translation in the console, it crushes everything else.
Do you know how to make that ?
Thanks !
You can just loop over all the enum attributes and get their values. Later you can merge and pass a new hash containing converted values
ENUM_COLUMNS = %i[way gender age] # Equivalent to [:way, :gender, :age]
def convert_enums
overrided_attributes = {}
ENUM_COLUMNS.each { |column| overrided_attributes[column.to_s] = self[column] }
attributes.merge(overrided_attributes)
end
NOTE:
While infosheet.gender returns you male or female
infosheet[:gender] will return you the respective integer value 0 or 1
You can test this if you use translate enum gem :
a = Infosheet.group(:gender).count
{“male”=>30, “female”=>6532}
Create a hash
r = Hash.new
And populate this with :
a.each {|s| puts r[Infosheet.translated_gender(s[0])]=s[1] }
r
result :
{“homme”=>30, “femme”=>6532}

Resources