Rails - Nearby cities in existing query which joins city table - ruby-on-rails-3

I have the following models:
class User
has_one :player
belongs_to :city
end
class Player
belongs_to :user
end
class City
has_many :users
end
Each City has a latitude and longitude attributes.
I have the following query (it's a little bit longer, but what matters is the city part) that I use to get players which associated users are from a particular city. As this:
def self.search(city)
self.includes(:user => :city).
where(['cities.name = ?', city])
end
How would I change this query to get professors which users associated cities are near by (10 kilometers for example) the given city?
EDIT: I have this method in the City model which gives me the closest city given a particular radius (in km), latitude and longitude:
def near_by(latitude,longitude,radius)
City.select("cities.*,
6371 *
acos(cos(radians(#{latitude})) *
cos(radians(cities.latitude)) *
cos(radians(cities.longitude) - radians(#{longitude})) +
sin(radians(#{latitude}))*sin(radians(cities.latitude)))
AS km_away").group('km_away ASC').having('km_away <= ?', radius)
end
EDIT2: Here is what I have so far in order to combine both queries:
# Pass a city object to the search instead of a city name, so we can use lat and long
def self.search(city)
self.includes(:user => :city).
where(['cities.id IN (?)', City.near_by(city.latitude,city.longitude)])
end
But is this a good approach? If so, how can I get the professors ordered by proximity to the passed city???

For working with geographic data, I recommend you check out the Geocoder gem as described in this railscast.
From the readme:
With geocoded objects you can do things like this:
obj.nearbys(30) # other objects within 30 miles
obj.distance_from([40.714,-100.234]) # distance from arbitrary point to object
obj.bearing_to("Paris, France") # direction from object to arbitrary point

Related

How to get records where associated's associated_id is a given value

I have a suburbs activerecord that belongs to a city and have many listings, I want to get the listings where the suburb city is a given value.
listings = Listing.joins(:suburbs).where('listings.updated_at >= ?', 2.months.ago).where('city_id = ?', city_id)
I am assuming you have these associations defined. Correct me if I am wrong.
In Suburb class:
has_many :listings
belongs_to :city
In Listing class:
belongs_to :suburb
In City class:
has_many :suburbs
Now to fetch all the listings where city is city_id, you need to join listings with suburb and then with city and then you need to filter out the results. Go through this link to understand better
listings = Listing.joins(suburb: :city).where('listings.updated_at >= ?', 2.months.ago).where(cities: {id: city_id })
This should work.
Find a suburb by given id
Find all related listings for the suburb. Check docs for more info.
Suburb.find(city_id).listings.where('updated_at >= ?', 2.months.ago)

How do I search an id in a string attribute (SQL)

In my chat app I want to calculate the response rate for student model.
I track all conversations with a slug attribute. It's a string like this: 270-77, which means that this is a conversation between student 270 and recruiter 77.
Now I want to check how many conversations one student has. Here is my code:
def calculate_number_of_conversations(#student)
#conversations = Conversation.where("slug LIKE ?", "%#{params[#student]}")
end
Important is that it should only search in the first part of the string because the first number in slug is always a student's id.
I'm not sure what #student is. I will write my examples as if it's a record.
You could use - to make sure it's looking for students only:
#conversations = Conversation.where('slug LIKE ?', "#{#student.id}-%")
But I think it's better to have explicit relationships:
class Conversation < ApplicationRecord
belongs_to :student
belongs_to :recruiter
end
#conversations = #student.conversations
You can add the '-' to the WHERE LIKE clause:
def calculate_number_of_conversations(#student)
#conversations = Conversation.where("slug LIKE ?", "%#{params[#student]}-")
end

Rails - Search for object based on association's value

I've got two models Company and InsuredObject. Company has_many InsuredObjects and the reverse belongs_to. Currently, I have a functioning search for InsuredObject(s) that returns all objects that contain the search input as shown below:
# /models/insured_objects.rb
def self.search(search)
query = "%#{search}%"
if search
where("object LIKE ? OR insurance_type LIKE ? OR class_code LIKE ? OR information LIKE ?",
query, query, query, query)
end
end
and:
# /controllers/insured_objects_controller.rb
def index
#insured_objects = InsuredObject.search(params[:search])
end
Each Company has a is_active attribute. I'm trying to think of a way to search for the same thing but only return InsuredObject(s) that their Company's is_active attrutbute is true. Any thoughts?
Get all entries of InsuredObject from active companies:
InsuredObject.joins(:company).where(companies: {is_active: true})
In joins(:company), :company is a name of association (in InsuredObject you should have belongs_to :company)
In where(companies: ..., :companies is a table name for model Company

Rails 3: has_many and where

I have the following models
class City < ActiveRecord::Base
has_many :dealers
end
class Dealer < ActiveRecord::Base
belongs_to :city
end
I need to find all the cities which have dealers. How do I go about writing the where() to get the desired results?
To find only the cities that have dealers:
City.joins(:dealers)
If a city has no dealers then it will not be returned in the results.
After playing around with some of the options this is what I came up with.
City.joins(:dealers).group("dealers.city_id")
this returns the complete records of the cities.
Edit after finding a better way.
Well there is a better way to achieve this as well and that is by using the uniq in the select. So the new query will be
City.joins(:dealers).uniq
which translates to
SELECT DISTINCT `cities`.* FROM `cities` INNER JOIN `dealers` ON `dealers`.`city_id` = `cities`.`id`

SQL: Get a selected row index from a query

I have an applications that stores players ratings for each tournament. So I have many-to-many association:
Tournament
has_many :participations, :order => 'rating desc'
has_many :players, :through => :participations
Participation
belongs_to :tournament
belongs_to :player
Player
has_many :participations
has_many :tournaments, :through => :participations
The Participation model has a rating field (float) that stores rating value (it's like score points) for each player at each tournament.
The thing I want - get last 10 ranks of the player (rank is a position of the player at particular tournament based on his rating: the more rating - the higher rank). For now to get a player's rank on a tournament I'm loading all participations for this tournament, sort them by rating field and get the player's participation index with ruby code:
class Participation < ActiveRecord::Base
belongs_to :player
belongs_to :tournament
def rank
tournament.participations.index(self)
end
end
Method rank of the participation gets its parent tournament, loads all tournamentr's participations (ordered by rating desc) and get own index inside this collection
and then something like:
player.participations.last.rank
The one thing I don't like - it need to load all participations for the tournament, and in case I need player ranks for last 10 tournaments it loads over 5.000 items (and its amount will grow when new players added).
I believe that there should be way to use SQL for it. Actually I tried to use SQL variables:
find_by_sql("select #row:=#row+1 `rank`, p.* from participations p, (SELECT #row:=0) r where(p.tournament_id = #{tournament_id}) order by rating desc limit 10;")
This query selects top-10 ratings from the given tournament. I've been trying to modify it to select last 10 participations for a given user and his rank.
Will appreciate any kind of help. (I think solution will be a SQL request, since it's pretty complex for ActiveRecord).
P.S. I'm using rails3.0.0.beta4
UPD:
Here is final sql request that gets last 10 ranks of the player (in addition it loads the participated tournaments as well)
SELECT *, (
SELECT COUNT(*) FROM participations AS p2
WHERE p2.tour_id = p1.tour_id AND p2.rating > p1.rating
) AS rank
FROM participations AS p1 LEFT JOIN tours ON tours.id = p1.tour_id WHERE p1.player_id = 68 ORDER BY tours.date desc LIMIT 10;
First of all, should this:
Participation
belongs_to :tournament
belongs_to :players
be this?
Participation
belongs_to :tournament
belongs_to :player
Ie, singular player after the belongs_to?
I'm struggling to get my head around what this is doing:
class Participation
def rank_at_tour(tour)
tour.participations.index(self)
end
end
You don't really explain enough about your schema to make it easy to reverse engineer. Is it doing the following...?
"Get all the participations for the given tour and return the position of this current participation in that list"? Is that how you calculate rank? If so i agree it seems like a very convoluted way of doing it.
Do you do the above for the ten participation objects you get back for the player and then take the average? What is rating? Does that have anything to do with rank? Basically, can you explain your schema a bit more and then restate what you want to do?
EDIT
I think you just need a more efficient way of finding the position. There's one way i could think of off the top of my head - get the record you want and then count how many are above it. Add 1 to that and you get the position. eg
class Participation
def rank_at_tour(tour)
tour.participations.count("rating > ?", self.rating) + 1
end
end
You should see in your log file (eg while experimenting in the console) that this just makes a count query. If you have an index on the rating field (which you should have if you don't) then this will be a very fast query to execute.
Also - if tour and tournament are the same thing (as i said you seem to use them interchangeably) then you don't need to pass tour to participation since it belongs to a tour anyway. Just change the method to rank:
class Participation
def rank
self.tour.participations.count("rating > ?", self.rating) + 1
end
end
SELECT *, (
SELECT COUNT(*) FROM participations AS p2
WHERE p2.tour_id = p1.tour_id AND p2.rating > p1.rating
) AS rank
FROM participations AS p1 LEFT JOIN tours ON tours.id = p1.tour_id WHERE p1.player_id = 68 ORDER BY tours.date desc LIMIT 10;