Select rows having max column value in rails - ruby-on-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.

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

Postgresql get sum of latest records for all distinct ids

I have the following table:
<id: 1, location: "New York" product_id: 1, amount: 10, date: "2020-06-23T20:04:12">
<id: 2, location: "New York" product_id: 1, amount: 20, date: "2020-06-20T00:00:45">
<id: 3, location: "New York" product_id: 2, amount: 50, date: "2020-06-19T23:26:21">
<id: 4, location: "New York" product_id: 3, amount: 50, date: "2020-06-24T00:00:00">
<id: 5, location: "Ottawa" product_id: 1, amount: 20, date: "2020-06-12T00:00:00">
<id: 6, location: "Ottawa" product_id: 1, amount: 30, date: "2020-06-22T00:00:00">
<id: 7, location: "Ottawa" product_id: 2, amount: 40, date: "2020-06-23T00:00:00">
I want to write a raw sql query that sums the total amount per location, but it only adds the amount once per product id. So, when there are records with similar product_id, the record it chooses, is the record closest to but less than the max_date variable. So for example, let's say that max_date = "2020-06-24T00:00:00", the response when querying the above table would be:
{
"New York":60, #exludes ids 2, 3
"Ottawa": 70 #exludes id 4
}
Any help would be great. Sorry if this is a noob question, kinda new with postgresql (or sql in general).
Please try this.
The keeps CTE limits the query to records before the cutoff "date" and assigns numbers to your rows.
The main query then picks only the ones where row_number() was 1, and calculates the sum by location.
with keeps as (
select location, product_id, amount,
row_number() over (partition by location, product_id
order by "date" desc) as rn
from sales
where "date" < '2020-06-24'
)
select location, sum(amount) as amount
from keeps
where rn = 1
group by location;

how to select fields from joined table properly?

Here are my simplified models, I have joined models:
class Apartment < ApplicationRecord
has_many :towers
end
and
class Tower < ApplicationRecord
belong_to :apartment
end
and then I tried to join both tables in controller. I also tried it in rails console like this :
Apartment.joins(:towers).select('apartments.id', 'apartments.name', 'towers.id' , 'towers.name')
the problem is above query only returns
apartments.id and apartments.name
also tried to use alias like this, still no luck
Apartment.joins(:towers).select('apartments.id', 'apartments.name',
'towers.id as towerid' , 'towers.name as towername')
I have confirmed that all towers have an apartment, I know i could do this to get 1 record
Apartment.joins(:towers).select('apartments.id', 'apartments.name',
'towers.id' , 'towers.name').first.towers.id
and etc, but i need all records and all those fields, please advice.
here is the latest result i got in rails console :
Apartment Load (1.0ms) SELECT apartments.id, apartments.name, towers.id as towerid, towers.
tower_name as towername FROM `apartments` INNER JOIN `towers` ON `towers`.`apt_id` = `
apartments`.`id`
=> #<ActiveRecord::Relation [#<Apartment id: 5, name: "basura">, #<Apartment id: 5, apt_
name: "basura">, #<Apartment id: 124, name: "hydra">, #<Apartment id: 124, name: "hy
dra">, #<Apartment id: 126, name: "mediterania">, #<Apartment id: 126, name: "mediterania">, #<Apartment id: 142, name: "apartement gajah mada">, #<Apartment id: 142, name: "apartement gajah mada">]>
as you can see, above query only return 2 fields, i need the result to be like this :
#<Apartment id: 126, name: "mediterania", tower_id: 12, tower_name: "tower A">,
#<Apartment id: 126, name: "mediterania", tower_id: 15, tower_name: "tower F">
etcc...
The only way I see this is possible is using as
q = Apartment.joins(:towers).select('apartments.id, apartments.name, towers.id as t_id, towers.name as t_name')
q.first.t_id
q.first.t_name
Why first.towers.id will not work?
apartment.towers will return ActiveRecord::Associations::CollectionProxy. You can think of it as a collection of towers. In SQL query you are referring to towers table. But when you run apartment.towers.id you are calling id on CollectionProxy object which will not work. You can get first tower using towers.first.
Regarding,
Apt Load (1.0ms) SELECT apts.id, apts.apt_name, towers.id as towerid, towers.
tower_name as towername FROM `apts` INNER JOIN `towers` ON `towers`.`apt_id` = `
apts`.`id`
=> #<ActiveRecord::Relation [#<Apt id: 5, apt_name: "basura">, #<Apt id: 5, apt_
name: "basura">, #<Apt id: 124, apt_name: "hydra">, #<Apt id: 124, apt_name: "hy
dra">, #<Apt id: 126, apt_name: "mediterania">, #<Apt id: 126, apt_name: "mediterania">, #<Apt id: 142, apt_name: "apartement gajah mada">, #<Apt id: 142, apt_name: "apartement gajah mada">]>
What you see in console is result returned by inspsect method. The inspect method is not designed to show non column attributes. Hence even if you have towername in memory it will only show attributes which are columns of Apartment model.
More about inspect
I also recommend to try following:
Apartment.joins(:towers).pluck('apartments.id, apartments.name, towers.id as t_id, towers.name as t_name')
Above statement will get all data in array. The same result you get with select but select will not load all data in array.
You should use
Apartment.joins(:towers).select('apartments.id, apartments.name, towers.id , towers.name')
that is all column names inside a single string.
Refer this.
Rails 7 adds support for such queries, now select accepts hash
You can use aliases like this
Apartment
.joins(:towers)
.select(
apartments: { id: :apartment_id, name: :apartment_name },
towers: { id: :tower_id, name: :tower_name }
)
It produces such query
SELECT apartments.id AS apartment_id, apartments.name AS apartments_name,
towers.id AS tower_id, towers.name AS tower_name
FROM apartments
INNER JOIN towers ON towers.apartment_id = apartments.id
You can try the alias names like below
Apartment.joins(:towers).select('apartments.id as apartment_id, apartments.name as apartment_name, towers.id as tower_id , towers.name as tower_name)
You can try this
Apartment.joins(:towers).select('apartments.id as id, apartments.name as apartment_name, towers.id as tower_id , towers.name as tower_name)
you will get the response like this
#ActiveRecord::Relation [#<Apt id: 126, apt_name: "mediterania", tower_id: 12, tower_name: "tower A">,
#<Apt id: 126, apt_name: "mediterania", tower_id: 15, tower_name: "tower F">]>

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

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