Rails named_scope with has_and_belongs_to_many - ruby-on-rails

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

Related

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]}%')

Rails - Join query difficulties

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)

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 HABTM joining with another condition

I am trying to get a list, and I will use books as an example.
class Book < ActiveRecord::Base
belongs_to :type
has_and_belongs_to_many :genres
end
class Genre < ActiveRecord::Base
has_and_belongs_to_many :books
end
So in this example I want to show a list of all Genres, but it the first column should be the type. So, if say a genre is "Space", the types could be "Non-fiction" and "Fiction", and it would show:
Type Genre
Fiction Space
Non-fiction Space
The Genre table has only "id", "name", and "description", the join table genres_books has "genre_id" and "book_id", and the Book table has "type_id" and "id". I am having trouble getting this to work however.
I know the sql code I would need which would be:
SELECT distinct genres.name, books.type_id FROM `genres` INNER JOIN genres_books ON genres.id = genres_books.genre_id INNER JOIN books ON genres_books.book_id = books.id order by genres.name
and I found I could do
#genre = Genre.all
#genre.each do |genre|
#type = genre.book.find(:all, :select => 'type_id', :group => 'type_id')
#type.each do |type|
and this would let me see the type along with each genre and print them out, but I couldn't really work with them all at once. I think what would be ideal is if at the Genre.all statement I could somehow group them there so I can keep the genre/type combinations together and work with them further down the road. I was trying to do something along the lines of:
#genres = Genre.find(:all, :include => :books, :select => 'DISTINCT genres.name, genres.description, books.product_id', :conditions => [Genre.book_id = :books.id, Book.genres.id = :genres.id] )
But at this point I am running around in circles and not getting anywhere. Do I need to be using has_many :through?
The following examples use your models, defined above. You should use scopes to push associations back into the model (alternately you can just define class methods on the model). This helps keep your record-fetching calls in check and helps you stick within the Law of Demeter.
Get a list of Books, eagerly loading each book's Type and Genres, without conditions:
def Book < ActiveRecord::Base
scope :with_types_and_genres, include(:type, :genres)
end
#books = Book.with_types_and_genres #=> [ * a bunch of book objects * ]
Once you have that, if I understand your goal, you can just do some in-Ruby grouping to corral your Books into the structure that you need to pass to your view.
#books_by_type = #books.group_by { |book| book.type }
# or the same line, more concisely
#books_by_type = #books.group_by &:type
#books_by_type.each_pair do |type, book|
puts "#{book.genre.name} by #{book.author} (#{type.name})"
end

Resources