Understanding grep within Enumerable module - ruby-on-rails

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

Related

How to get the data using find_by Or where in rails?

Truck
id: 1,
vehicle_registration_number: "TN38CC6077",
chassis_number: "12345",
created_at: "2016-09-06 05:39:19",
updated_at: "2016-09-06 05:39:19",
company_truck_type_id: 1,
location_id: 492,
available_date: ["2016-09-10",
"2016-09-20"],
booked_status: "Available",
active: true,
manager_id: 3,
loading_supervisor_id: 3,
transport_supervisor_id: 3,
user_id: 3,
status: "just_in",
price: #<BigDecimal:afae0204,'0.109E5',9(18)>,
source_id: 492,
destination_id: 3,
notes: ["HelloWorld"]
This is my data in a table. From Here I have use available_date to find a current data,but it can't working. How can I get based on current date?
#truck[0].where(available_date:["2016-09-10"]["2016-09-20"])
It's not working. I want to check if the current date is between the available_date or not?
you can do something like below:
Truck.where(available_date: (Time.now.midnight - 1.day)..Time.now.midnight)
Hope that helps you.
Try this code snippets ;
def current_date_is_available?(available_date)
current_date = Date.parse(Time.now.to_s)
Date.parse(available_date.first) < current_date && current_date < Date.parse(available_date.second)
end
in controller ;
if current_date_is_available?(#truck[0].available_date)
# do whatever you want
end

Select rows having max column value in rails

I have an array of ActiveRecord of table Person (:id, :name, :city_id, :work_id, :date). For eg :
[<Person id: 4, name: John, city_id: 10, work_id: 1, date: 2015-10-10>,
<Person id: 5, name: Jack, city_id: 11, work_id: 2, date: 2015-10-08>,
<Person id: 8, name: John, city_id: 10, work_id: 2, date: 2015-10-11>,
<Person id: 9, name: John, city_id: 10, work_id: 3, date: 2015-10-11>,]
For a particular person, I want the rows with the maximum date. So, for Jack, I'll get row with id = 5. For John, I'll get the rows with id = [8,9] as the date is maximum for these rows. I can't figure out an efficient way to to this in Rails. Please help.
You can use maximum class method of rails.
Try this:
Person.find_all_by_date(self.maximum('date'))
See some details here.

Splitting an array into sub arrays 2

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 << []

Rails how to calculate multiple sums from one query data

With the following two models, Company and Response, I am making a query of the total responses per company like this:
#allResponses = Company.find(current_user_company_id).responses
this gives me data like this:
[#<Response id: 1, company_id: 1, created_at: "2013-04-24 02:36:54", feedback_score: 10, feedback_explanation: "I really like the way you guys do xyz.", additional_data: "", updated_at: "2013-04-24 02:36:54">, #<Response id: 2, company_id: 1, created_at: "2013-04-25 03:51:07", feedback_score: 5, feedback_explanation: "customer service is spotty.", additional_data: "", updated_at: "2013-04-25 03:51:07">, #<Response id: 3, company_id: 1, created_at: "2013-04-25 03:52:04", feedback_score: 7, feedback_explanation: "You've got potential.", additional_data: "", updated_at: "2013-04-25 03:52:04">, #<Response id: 4, company_id: 1, created_at: "2013-04-25 03:52:18", feedback_score: 9, feedback_explanation: "Almost perfect.", additional_data: "", updated_at: "2013-04-25 03:52:18">]
I want to get the following two variables out of this data:
#sumOfHighScores = some.thing.here #sum of feedback_scores that are greater than 8
#sumOfLowScores = some.thing.here.too #sum of feedback_scores that are less than 7
You can try this,
#sumOfHighScores = #allResponses.select{ |response| response.feedback_score > 8 }.map(&:feedback_score).sum
#sumOfLowScores = #allResponses.select{ |response| response.feedback_score < 7 }.map(&:feedback_score).sum
Try this..
#sumOfHighScores = #allResponses.select{ |response| response.feedback_score > 8 }.sum
#sumOfLowScores = #allResponses.select{ |response| response.feedback_score < 7 }.sum
I will perform the entire calculation in the database.
company = Company.find(current_user_company_id)
totals = company.responses.sum(
:feedback_score,
:group => "CASE WHEN feedback_score < 7 THEN 'low' ELSE 'high' END")
low, high = (totals['low'] || 0), (totals['high'] || 0 )

Rails 3: use contents of an array as variables in a where method

I have three models: Isbn, Sale and Channel.
Aim: to get a list in the isbns show.html.erb view which looks something like this:
Isbn: myisbn
Total sales for myisbn: 100
Myisbn sales for channel 1: 50
Myisbn sales for channel 2: 25
Myisbn sales for channel 3: 25
Here are my models.
Isbn.rb model
has_many :sales
has_many :channels, :through => :sales
Sale.rb model (has attributes sales_channel_id, isbn_id, quantity)
has_many :channels
belongs_to :isbn
Channel.rb model:
belongs_to :sale
I've been working in the isbns controller, in the show method, just to get something to work. I thought I'd refactor later - advice on whether any of this stuff should go in the model would be most welcome.
So far I've got this:
#channelisbn = Sale.where("sales_channel_id =?',1).where("isbn_id=?",3)
#channelsalesisbn = 0
#channelisbn.each {|y| #channelsalesisbn =+ y.quantity}
This successfully gets all the sales where Channel ID is 1 and ISBN id is 3. But it's not much use, as the IDs are hard coded. So I got the Channel IDs into an array:
#channellist = Channel.all
#channel = 0
#channelarray = #channellist.map {|z| #channel = z.id}
which gives me a lovely array of [1,2,3,4]
But I can't figure out how to pass the 1, then the 2, then the 3 and then the 4 into a block which can be used to look up an ISBN's sales which have that sales channel id. This is what I tried (still hardcoding the ISBN id - thought I'd tackle one problem at a time), which returned an empty array:
#channelarray.each do |channel|
#channelisbn = []
#channelisbn = Sale.where("sales_channel_id = ?", channel).where("isbn_id = ?",3)
#channelsalesisbn = 0
#result = []
#result << #channelisbn.each {|a| #channelsalesisbn =+ a.quantity}
end
I was then going to sum the contents of the array.
Any help would be gratefully received. This is my first post, so my zero acceptance rate will change soon!
UPDATE
Just to finish this question off, here's where I've ended up, which is great, and ready for tinkering with: an array, nicely grouped, giving me sales by isbn by channel. Thanks for the group_by tip off!
#in the show action in the isbns controller:
#isbn = Isbn.find(params[:id])
#channelarray = Channel.select(:id).all
#channelarray.group_by {|i| Sale.where("channel_id = ?",i).where("isbn_id =?", #isbn)}
From the console, line breaks added for clarity:
(sneakily set #isbn = 3 first of all, since in the console you can't pass params from a view, so the #isbn instance defined in the controller is nil in the console)
ruby-1.9.2-p180 :067 > #channelarray.group_by {|i| Sale.where("channel_id = ?",i).where("isbn_id =?", #isbn)}
=> {[#<Sale id: 1, isbn_id: 3, quantity: 10000, value: 12000, currency: "GBP", total_quantity: nil, created_at: "2011-05-06 12:30:35", updated_at: "2011-05-07 17:43:13", customer: "Waterstone's", retail_price: nil, discount: nil, invoice_date: "2011-05-24">, #<Sale id: 2, isbn_id: 3, quantity: 1000, value: 500, currency: "GBP", total_quantity: nil, created_at: "2011-05-07 09:37:53", updated_at: "2011-05-07 19:14:52", customer: "Borders", retail_price: nil, discount: nil, invoice_date: "2011-02-05">]=>[#<Channel id: 1>],
[#<Sale id: 3, isbn_id: 3, quantity: 500, value: 1500, currency: "", total_quantity: nil, created_at: "2011-05-07 09:38:11", updated_at: "2011-05-07 19:15:07", customer: "Borders", retail_price: nil, discount: nil, invoice_date: "2011-12-05">, #<Sale id: 4, isbn_id: 3, quantity: 45, value: 300, currency: "", total_quantity: nil, created_at: "2011-05-07 09:38:38", updated_at: "2011-05-07 19:15:36", customer: "Borders", retail_price: nil, discount: nil, invoice_date: "2011-06-05">]=>[#<Channel id: 2>],
[]=>[#<Channel id: 3>],
[]=>[#<Channel id: 4>]}
UPDATE 2
Ha, the hash I generated had the key value pairs the wrong way round. The array containing the sales data was the key - it should have been the value. Rubydocs saved the day:
#salesbychannel = #salesbychannelwrong.invert
The invert method switches the key-value pairs. Sweet.
What you're looking for is passing an array to a ARel#where(), like this:
Sale.where(:sales_channel_id => #channelarray)
This should execute an IN query. If that's not working, you can always pass the array to ActiveRecord#find, like this:
Sale.find(#channelarray)
Hope this helps

Resources