Rails - Join query difficulties - ruby-on-rails

I have two basic models: Stadium and Owner
The relations between those two models are:
A Stadium:
belongs_to :owner
An Owner:
has_many :stadiums
The thing here is, an Owner has also Categories associated, and here is where owner_category model comes in.
An Owner:
has_and_belongs_to_many :owner_categories,
:join_table => 'l_owners_owner_categories',
And OwnerCategory:
has_and_belongs_to_many :owners, :join_table => 'l_owners_owner_categories'
Basically,
The OwnerCategory table looks like this:
id,name
1,sport
2,kids
So, my question is:
Given that I let the user to choose a City and a Category, how would I get all the Stadiums from that city which Owner has the given Category?
So for example:
If I have the following Stadiums:
id,name,city,owner_id
1,'soccer stadium','New York',5
2,'music stadium','New York',4
2,'music stadium','San Francisco',4
The following Owners:
id,name
4, 'John'
5, 'Peter'
The following OwnersCategories table:
id,name,description
1,'sports','this category is associated with stadiums that support sports'
2,'music','this category is associated with stadiums that support music'
And the following join table:
owner_id,owner_category_id
5, 1
When the users chooses 'New York' and 'Sports' it should give this stadium:
1,'soccer stadium','New York',5

Try something like this (warning: not tested):
Stadium.joins(:owner).joins(:owner => :owner_categories)
.where(:city => "New York").where("owners_categories.name = ?", "sports")

class Stadium < ActiveRecord::Base
scope :for_city, lambda { |city_name| where(:city => city_name) }
scope :for_owner_category,
lambda { |owner_category_id|
joins("INNER JOIN owners ON owners.id = stadium.owner_id" +
" INNER JOIN l_owners_owner_categories ON l_owners_owner_categories.owner_id = owners.id").
where("l_owners_owner_categories.owner_category_id = :category_id",
:category_id => owner_category_id)
}
end
Stadium.for_city("New York").for_owner_category(1)

Related

How to combine two joins.where in rails

Help to join the query in one large, well, or grammatically, so that for each company there are dot applications in which
Here are 2 separately working query strings.
DotApplication.all.joins(:current_stage_event).where(current_stage_event: {event: "prospect"}).joins('LEFT JOIN tasks on tasks.subject_id = dot_applications.id').where(tasks: {id: nil})
Company.all.joins(:users).where(users: { notification_for_active_prospects: true})
What i want:
Company.each do |company|
# For each company(with joins,where above), find all dot_applications as in the examples above
end
The association between Company and DotApplication looks like this:
# app/models/company.rb
class Comany < ApplicationRecord
has_many :dot_aplications
end
# app/models/dot_application.rb
class DotApplication < ApplicationRecord
belongs_to :company
end
You can join on nested associations like this joins(company: :users)
In your case this might do the job
DotApplication.joins(company: :user).where(users: { notification_for_active_prospects: true})
Or full version
DotApplication.all.joins(:current_stage_event, company: :user).where(current_stage_event: {event: "prospect"}).joins('LEFT JOIN tasks on tasks.subject_id = dot_applications.id').where(tasks: {id: nil}).where(users: { notification_for_active_prospects: true})
But I really don't recommand you to do this at it is totally unmaintainable
You can read this post of you want to know more about joins https://www.learneroo.com/modules/137/nodes/768
Company.joins(:dot_applications)
.where(dot_appplications: DotApplication.joins(:current_stage_event)
.where(current_stage_event: {event: "prospect"})
.joins('LEFT JOIN tasks on tasks.subject_id = dot_applications.id')
.where(tasks: {id: nil}))
.joins(:users)
.where(users: { notification_for_active_prospects: true})
This should find all the companies meeting your requirements for the DotApplication and then query those companies for the User requirements

Count distinct records while plucking from another table

Given Rails app where:
class Team < ActiveRecord::Base
belongs_to :captain, class_name: User
I'm trying to succinctly query database and get a summary of Captains and the number of Teams they belong to:
[[23, "Pat", "Smith"]] Teams: 1
[[46, "Kate", "Jones"]] Teams: 1
[[107, "John", "Doe"]] Teams: 3
...
I can get a hash with each Captain's ID and the number of teams:
> Team.group(:captain_id).count
=> {85=>3, 106=>1, 81=>1, 25=>1, 32=>1, 8=>3, 1=>1, 79=>2, 26=>1}
But then it gets ugly. The following works but seems ugly and has n+1 SQL queries:
Team.group(:captain_id).count.sort.to_h.each { |key,val| puts "#{User.where(id: key).pluck(:id, :first, :last)} Teams: #{val}" }
What's the Rails way?
Since you are trying to get detailed info about users, you may want to define a relation like this on your User model:
class User < ActiveRecord::Base
has_many :captained_teams, foreign_key: :captain_id, class_name: Team
Then you can work with User for your query:
User.joins(:captained_teams).includes(:captained_teams).each do |user|
puts "#{[user.id, user.first, user.last]} Teams: #{user.captained_teams.length}"
end
sql = "**Select count then group by then having count(...)**"
result = ActiveRecord::Base.connection.execute(sql);
You can use the raw sql to get what you want.

rails: query and filter n:m related objects using active record

I'm trying to use active-record query possible connections between airports.
I described the models I created already in another question here:
n:m self-join with ruby on rails active record
Basically, what I can do now is that:
ny = Airport.create({"city" => "New York"})
la = Airport.create({"city" => "Los Angeles"})
ny.destinations << la
la.destinations << ny
I ran into an issue querying the data I'm looking for, which is quite simple in SQL but I had no luck with active record yet.
ny = Airport.where('city = ?', 'New York')
ny.destinations
returns the correct objects, but all of them.
The SQL query looks like that:
SELECT "airports".* FROM "airports" INNER JOIN "connections" ON "airports"."id" = "connections"."destination_id" WHERE "connections"."airport_id" = 3
I'd like to filter those results by cities starting with "s" for example, so an SQL query could look like that:
SELECT "airports".* FROM "airports" INNER JOIN "connections" ON "airports"."id" = "connections"."destination_id" WHERE "connections"."airport_id" = 3 AND airports"."city" LIKE "s%"
I tried it this way:
ny.destinations.where('city LIKE ?', '#{params[:query]}%')
But I always get an empty result.
How could I use active record to filter my resulting objetcs?
edit: Thats the best solution I found so far:
I added the cityLike() method to the Airport model:
app/models/airport.rb:
class Airport < ActiveRecord::Base
attr_accessible :city, :name
has_many :connections
has_many :destinations, :through => :connections
has_many :inverse_connections, :class_name => "Connection", :foreign_key => "destination_id"
has_many :inverse_destinations, :through => :inverse_connections, :source => :airport
def self.cityLike(query)
where("city LIKE ?", "%#{query}%")
end
end
app/model/connection.rb:
class Connection < ActiveRecord::Base
attr_accessible :destination_id, :airport_id
belongs_to :airport
belongs_to :destination, :class_name => "Airport"
end
Now I can query the objects with the following statement:
Airport.find(1).destinations.cityLike("a")
Not sure if it's the best solution, but it produces the query I was looking for.
Thanks a lot to all af you!
ActiveRecord::Base.execute(sql) lets you use pure SQL to do your query and returns the relevant model.
What about this?
Airport.find(:all, joins: "INNER JOIN `connections` ON airports.id = connections.destination_id").where("connections.airport_id = ? AND airports.city LIKE ?", ny_id, "s%")
This code:
ny.destinations.where('city LIKE ?', '#{params[:query]}%')
works like this--first you have an object ny repesenting the city of New York. When you say ".destinations" you have now followed a relation you defined in your model to retrieve all the destinations that you can get to from New York. However, if I'm imagining your database schema correctly, these destinations don't actually have a field called "city"; instead, they have a destination_id, which ties the destination to a particular airport, and it's the airport that has a city associated with it.
So when you query the destination table for 'city LIKE ?', it doesn't find any matching records.
Instead, try
ny.destinations.joins(:airports).where('city LIKE ?', '#{params[:query]}%')

ActiveRecord Association select counts for included records

Example
class User
has_many :tickets
end
I want to create association which contains logic of count tickets of user and use it in includes (user has_one ticket_count)
Users.includes(:tickets_count)
I tried
has_one :tickets_count, :select => "COUNT(*) as tickets_count,tickets.user_id " ,:class_name => 'Ticket', :group => "tickets.user_id", :readonly => true
User.includes(:tickets_count)
ArgumentError: Unknown key: group
In this case association query in include should use count with group by ...
How can I implement this using rails?
Update
I can't change table structure
I want AR generate 1 query for collection of users with includes
Update2
I know SQL an I know how to select this with joins, but my question is now like "How to get data" . My question is about building association which I can use in includes. Thanks
Update3
I tried create association created like user has_one ticket_count , but
looks like has_one doesn't support association extensions
has_one doesn't support :group option
has_one doesn't support finder_sql
Try this:
class User
has_one :tickets_count, :class_name => 'Ticket',
:select => "user_id, tickets_count",
:finder_sql => '
SELECT b.user_id, COUNT(*) tickets_count
FROM tickets b
WHERE b.user_id = #{id}
GROUP BY b.user_id
'
end
Edit:
It looks like the has_one association does not support the finder_sql option.
You can easily achieve what you want by using a combination of scope/class methods
class User < ActiveRecord::Base
def self.include_ticket_counts
joins(
%{
LEFT OUTER JOIN (
SELECT b.user_id, COUNT(*) tickets_count
FROM tickets b
GROUP BY b.user_id
) a ON a.user_id = users.id
}
).select("users.*, COALESCE(a.tickets_count, 0) AS tickets_count")
end
end
Now
User.include_ticket_counts.where(:id => [1,2,3]).each do |user|
p user.tickets_count
end
This solution has performance implications if you have millions of rows in the tickets table. You should consider filtering the JOIN result set by providing WHERE to the inner query.
You can simply use for a particular user:
user.tickets.count
Or if you want this value automatically cached by Rails.
Declare a counter_cache => true option in the other side of the association
class ticket
belongs_to :user, :counter_cache => true
end
You also need a column in you user table named tickets_count.
With this each time you add a new tickets to a user rails will update this column so when you ftech your user record you can simply accs this column to get the ticket count without additional query.
Not pretty, but it works:
users = User.joins("LEFT JOIN tickets ON users.id = tickets.user_id").select("users.*, count(tickets.id) as ticket_count").group("users.id")
users.first.ticket_count
What about adding a method in the User model that does the query?
You wouldn't be modifying the table structure, or you can't modify that either?
How about adding a subselect scope to ApplicationRecord:
scope :subselect,
lambda { |aggregate_fn, as:, from:|
query = self.klass
.select(aggregate_fn)
.from("#{self.table_name} _#{self.table_name}")
.where("_#{self.table_name}.id = #{self.table_name}.id")
.joins(from)
select("(#{query.to_sql}) AS #{as}")
}
Then, one might use the following query:
users = User.select('users.*').subselect('COUNT(*)', as: :tickets_count, from: :tickets)
users.first.ticket_count
# => 5

Rails named_scope with has_and_belongs_to_many

i have 3 tables - films, films_genres (for connect 2 tables) and genres.
In Model film.rb - has_and_belongs_to_many :genres
In Model genre.rb - has_and_belongs_to_many :films
So, how I can write this sql code:
SELECT * FROM genres INNER JOIN films_genres ON genres.id = films_genres.genre_id WHERE (films_genres.film_id = 1 )
with named_scope in Model film.rb for show all film rolled genres?
class Model < ActiveRecord::Base
named_scope :by_genre, lambda { |*genres|
{
:include => :genres,
:conditions => [ "genres.id IN (?)", genres.map(&:id) ]
}
}
end
Film.by_genre(western, sci_fi).find(:all)
I made this one slightly more complex in order to specify multiple genres as part of your named scope. Hope it helps.
In English, what are you trying to pull from the DB? To retrieve the genres for a particular film just do:
#genres = #film.genres
chap! I try to pool from the db list of genres connected to the film. And my question is: how I can to do this using named_scome in film.rb Model? I need this for films filter.
Link for example: http://clearcove.ca/blog/2008/12/recipe-restful-search-for-rails/#more-218
Databases:
films:
id
name
descr
year
films_genres:
id
film_id
genre_id
genres:
id
name

Resources