Using where clause with a foreign key - ruby-on-rails-3

In my app I have a search field to find football visits. I want it to be able to search by the club of the visit. In my model visit belongs_to club, so visit has a field "club_id". So when I search with an ID, it finds the club, but I want to be able to find it by club name. "club_id" should be replaces by something like "visit.club.name", but how can I achieve this? This is my current query:
# Find visits
def find_anything(find_phrase)
unless find_phrase.blank?
#visits = Visit.where('address LIKE ? OR ground LIKE ? OR club_id LIKE ?',
"%#{find_phrase}%",
"%#{find_phrase}%",
"%#{find_phrase}%")
end
end

that should do the trick:
Visit.where(club_id: Club.where(name: "ClubName"))

Club.joins(:visits).where('visits.club_id' => club_id).first.name
*this assumes the Club model has_many :visits
or
Club.find(Visit.find_by_club_id(club_id)).name

Related

How to select parent by most recent child with Activerecord

I have a user model which has many subscriptions. I need to make two selections:
active users defined as user with a subscription in the last month
inactive users the ones that don't meet the (1) criteria
My subscription model has a simple scope .latest which is defined as ordered("created_at DESC").first.
To make selection (1) I use:
User.joins(:subscriptions).where("subscriptions.created_at > ?", 1.month.ago).distinct
This works, no problem there. However, I can't seem to define a working query for selection (2). Currently I use selection (1) and 'subtract' that from User.all to get the remaining users. This feels a bit like a hack.
The selection I need is:
all users whose most recent subscription was created more than 1 month ago
It's the most recent part of the query that has me stuck.
Any help appreciated.
Quick & dirty way: use complex SQL like this
Assume you are using auto incremental ID
User.select("users.*, MAX(subscriptions.id) as last_subscription_id")
.joins(:subscriptions)
.group("subscriptions.user_id")
.having("last_subscription_id = (select id from subscriptions where user_id=users.id and created_at < '2017-10-01 09:23:28.182475' order by id desc limit 1)")
Recommended way
Add last_subscription_id to users table and setup a belongs_to relationship user belongs_to last_subscription then do the joins normally. You need to update last_subscription_id of an user when new subscription for this user is created too.
Example: User class looks like this (I include has_many :subscriptions to show that we have 2 relations now)
class User < ActiveRecord::Base
has_many :subscriptions
belongs_to :last_subscription, class_name: 'Subscription', foreign_key: :last_subscription_id
end
And query will be
User.joins(:last_subscription).where("subscriptions.created_at < ?", 1.month.ago)
for the most recent part you can do this.
User.joins("LEFT JOIN subscriptions ON subscriptions.user_id = users.id").order("subscriptions.created_at DESC").select("subscriptions.created_at AS max_date").group_by(&:id).select{|key, value| value[0].max_date < 1.month.ago}.values.flatten

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

Find records with the condition from other model rails

I have Bank and Rating models. Bank has many ratings. Also, bank can be active and inactive (when it's license is suspended).
Inactive bank has date field (license_suspended) in DB with the date, when license was suspended. Active banks has nil in this field.
I need to find ratings only for active banks. I can find all banks with license_suspended: nil and then find associated rating with current date and add it one-by-one to array, but I think there is a better way to do it. I need something like this:
#ratings = Rating.where(date: Date.today.beginning_of_month, bank: bank.license_suspended.blank?)
Thanks!
class Bank < ActiveRecord::Base
has_many :ratings
scope :active, -> { where(license_suspended: nil) }
end
class Rating < ActiveRecord::Base
belongs_to :bank
end
I think this will do what you want:
Rating.joins(:bank).where(date: Date.today).where(bank: {license_suspended: nil})
Or this:
Rating.joins(:bank).where(date: Date.today).merge(Bank.active) //this way you reuse active scope from Bank model
This will result in the following query:
SELECT "ratings".* FROM "ratings" INNER JOIN "banks" ON "banks"."id" = "ratings"."bank_id" WHERE "ratings"."date" = 'today_date' AND banks.license_suspended IS NULL
Assuming Ratings belong to a Bank, this looks like it'll do what you want:
Rating.joins(:bank).where(date: Date.today.beginning_of_month, bank: {license_suspended: nil}

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;