Let me describe what i want to do:
There is a Match model, which should have info about what players and what clans attended in it, with division of home players and clan and away players and clan.
That is pretty easy, but there is another model: Summoner. In each match every player has different summoner and I need to do something like this: Match.find(1).players_home.find(1).Summoner.name to extract which summoner played player 1 in home team.
The point is: each player in each match can play with different summoner.
I hope I described it clearly.
Regards.
I'm not really sure about all your specifications regarding when an association is one or several, but I think something like this could be it:
class Match
has_many :participations
has_many :players, :through => :participations
end
class Participation
belongs_to :match
belongs_to :player
belongs_to :summoner
# also a team attribute to store either "home" or "away"
scope :home, where(:team => "home")
scope :away, where(:team => "away")
end
class Player
belongs_to :clan
has_many :participations
has_many :matches, :through => :participations
end
class Summoner
has_many :participations
end
In this setup every match has several participations. Every participation belongs to the player that is participating and also belongs to a summoner for that player and match. It can then be utilized perhaps like this:
In Controller
#match = Match.find(1)
#home_participations = #match.participations.home
#away_participations = #match.participations.away
In View
<h1>Home Players</h1>
<% #home_participations.each do |p| %>
<p>Player: <%= p.player.name %>, Summoned by: <%= p.summoner.name %></p>
<% end %>
I hope this was at least somewhat what you where going for. Let me know if you are looking for something else.
Related
The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
I need to display the games, that belongs to a specific provider. I tried to do it like:
<% #provider.categorizations.joins(:games).each do |game| %>
<%= game.title %>
<% end %>
It gives me an error: NoMethodError: undefined method 'title' for #<Categorization:0x007f2cf6ee49e8>. So, it loops through the Categorization. What is the best way to loop through the joined games table? Thanks.
First, you should do the request in your controller, or even better call a scope (defined in a model) from the controller.
Do not forget that Active Record is just an ORM, a tool allowing you to manipulate SQL.
With #provider.categorizations.joins(:games) you are not asking for games. You are asking for the categorizations and you do a JOIN with the games table. This joins is usually to allow to filter by games attributes.
To do what you want you should do the following :
#games = Game.joins(:categorization).where('categorization.provider_id = ?',#provider.id)
As you can see, the join do not return categorization, it allow me to use categorization as a filter.
You should always be aware of the SQL generated by Active Record. Look at the SQL query generated in your server's traces.
I'm guessing 'title' is an attribute of games and not categorization, so you either need to return an array of games, or add a select on the end to pull the title attribute into the categorization object, like so:
<% #provider.categorizations.joins(:games).select('dba.games.title').each do |game| %>
<%= game.title %>
<% end %>
Just to add- you shouldn't really be doing this in the view file. I'd go as far as not even doing this in the controller. I tend to encapsulate this sort of logic into a service class, which is instantiated in the controller to return a set of results. The controller should only be passing the result set on, which is then presented by the view.
class Provider < ActiveRecrord::Base
# this could be a scope instead, or in a seperate class which
# the provider model delegates to- whatever floats you boat
def get_games
# you could use pluck instead, which would return an array of titles
categorizations.joins(:games).select('dba.games.title')
end
end
class ProviderController < ApplicationController
def show
provider = Provide.find(params[:id])
#games = provider.get_games
end
end
<% #games.each do |game| %>
<%= game.title %>
<% end %>
I have a these models:
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
What I need now is in the page of "Cinemas" I wanna print the sum (count, size?) of the childrens just for the movies of that cinemas, so I wrote this:
in the cinemas_controller.rb:
#childrens = #cinema.childrens.uniq
in the cinemas/show.html.erb:
<% #childrens.each do |children| %><%= children.movies.size %><% end %>
but obviously I have bullet gem that alert me for Counter_cache and I don't know where to put this counter_cache because of different id for the movie.
And also without the counter_cache what I have is not what I want because I want a count for how many childrens in that cinema taking them from the tickets from many days in that cinema.
How to?
UPDATE
If in my view I use this code:
<% #childrens.each do |children| %>
<%= children.movies.where(cinema_id: #cinema.id).size %>
<% end %>
gem bullet don't say me anything and every works correctly.
But I have a question: this way of querying the database is more heavy because of the code in the views?
This might help you.
#childrens_count = #cinema.childrens.joins(:movies).group("movies.children_id").count.to_a
You can use includes to load all associations ahead of time. For example:
#childrens = #cinema.childrens.includes(:movies).uniq
This will load all of the children's movies in the controller, preventing the view from needing access to the database in your loop.
You might agree, that the number of movies belongs to a child equals the number of tickets they bought.
That's why you could just cache the number of tickets and show it on the cinemas#show.
You can even create a method to make it more clear.
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
def movies_count
tickets.size
end
end
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children, counter_cache: true
end
class Movie < ActiveRecord::Base
belongs_to :cinema
has_many :tickets
has_many :childrens, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
And then:
<% #childrens.each do |children| %><%= children.tickets.size %><% end %>
Or
<% #childrens.each do |children| %><%= children.movies_count %><% end %>
But if you want to show the number of tickets for every movie, you definitely need to consider the following:
#movies = #cinema.movies
Then:
<% #movies.each do |movie| %><%= movie.tickets.size %><% end %>
Since you have belongs_to :movie, counter_cache: true, tickets.size won't make a count query.
And don't forget to add tickets_count column. More about counter_cache...
P.S. Just a note, according to conventions we name a model as Child and an association as Children.
Actually is much more simpler than the remaining solutions
You can use lazy loading:
In your controller:
def index
# or you just add your where conditions here
#childrens = Children.includes(:movies).all
end
In your view index.hml.erb:
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
The code above won't make any extra query if you use size but if you use count you will face the select count(*) n + 1 queries
I wrote a little ActiveRecord plugin some time ago but haven't had the chance to publish a gem, so I just created a gist:
https://gist.github.com/apauly/38f3e88d8f35b6bcf323
Example:
# The following code will run only two queries - no matter how many childrens there are:
# 1. Fetch the childrens
# 2. Single query to fetch all movie counts
#cinema.childrens.preload_counts(:movies).each do |cinema|
puts cinema.movies.count
end
To explain a bit more:
There already are similar solutions out there (e.g. https://github.com/smathieu/preload_counts) but I didn't like their interface/DSL. I was looking for something (syntactically) similar to active records preload (http://apidock.com/rails/ActiveRecord/QueryMethods/preload) method, that's why I created my own solution.
To avoid 'normal' N+1 query issues, I always use preload instead of joins because it runs a single, seperate query and doesn't modify my original query which would possibly break if the query itself is already quite complex.
In You case You could use something like this:
class Ticket < ActiveRecord::Base
belongs_to :movie, counter_cache: true
belongs_to :children
end
class Movie < ActiveRecord::Base
has_many :tickets
has_many :childrens, through: :tickets
belongs_to :cinema
end
class Children < ActiveRecord::Base
has_many :tickets
has_many :movies, through: :tickets
end
class Cinema < ActiveRecord::Base
has_many :movies, dependent: :destroy
has_many :childrens, through: :movies
end
#cinema = Cinema.find(params[:id])
#childrens = Children.eager_load(:tickets, :movies).where(movies: {cinema_id: #cinema.id}, tickets: {cinema_id: #cinema.id})
<% #childrens.each do |children| %>
<%= children.movies.count %>
<% end %>
Your approach using counter_cache is in right direction.
But to take full advantage of it, let's use children.movies as example, you need to add tickets_count column to children table firstly.
execute rails g migration addTicketsCountToChildren tickets_count:integer,
then rake db:migrate
now every ticket creating will increase tickets_count in its owner(children) by 1 automatically.
then you can use
<% #childrens.each do |children| %>
<%= children.movies.size %>
<% end %>
without getting any warning.
if you want to get children count by movie, you need to add childrens_count to movie table:
rails g migration addChildrensCountToMovies childrens_count:integer
then rake db:migrate
ref:
http://yerb.net/blog/2014/03/13/three-easy-steps-to-using-counter-caches-in-rails/
please feel free to ask if there is any concern.
Based on sarav answer if you have a lot of things(requests) to count you can do:
in controller:
#childrens_count = #cinema.childrens.joins(:movies).group("childrens.id").count.to_h
in view:
<% #childrens.each do |children| %>
<%= #childrens_count[children.id] %>
<% end %>
This will prevent a lot of sql requests if you train to count associated records
I've got two different types of users here, Fans and Artists.
I have a Relationships model to allow Fans to follow Artists.
Creating the relationship is working fine, but I now need to check if a Fan is following an Artist.
I also have add_index :relationships, [:fan_id, :artist_id], unique: true in my database, so a Fan cannot follow an Artist multiple times and displays an error if they try to follow again.
Now when a Fan clicks the follow button I want an unfollow button to show. To display this I need to check if a Fan is following an Artist.
Here is my code:
### model/artist.rb ###
class Artist < ActiveRecord::Base
has_many :relationships
has_many :fans, through: :relationships
belongs_to :fan
end
### model/fan.rb ###
class Fan< ActiveRecord::Base
has_many :relationships
has_many :artists, through: :relationships
belongs_to :artist
def following?(artist)
Fan.includes(artist)
end
end
### relationship.rb ###
class Relationship < ActiveRecord::Base
belongs_to :fan
belongs_to :artist
end
### views/artists/show.html.erb ###
<% if current_fan.following?(#artist) %>
unfollow button
<% else %>
follow button
<% end %>
I'm 100 percent the error is in my "following?" method.
As Jordan Dedels said, this will work:
def following?(artist)
artists.include?(artist)
end
But it forces rails to either load the join models, or to use a join query.
If you know the structure of your associations, and you only want a boolean (true/false), then this is faster:
def following?(artist)
Relationship.exists? fan_id: id, artist_id: artist.id
end
Inside your Fan model, try:
def following?(artist)
artists.include?(artist)
end
I have Four models.
Product
Category
Categorization
Images
Product.rb
has_many :images
has_many :categorizations
has_many :categories, :through => :categorizations
Category.rb
has_many :categorizations
has_many :products, :through => :categorizations
Categorization.rb
belongs_to :category
belongs_to :product
Image.rb
belongs_to :product
When a user clicks to see a product, I want to have a section on the bottom that shows images similar products (based on same category/categories).
I guess my problem is that there are so many nested relationships that I don't know how to extract the relationships out.
Any help is appreciated. Thanks.
Just think of what object you are currently working with and what object you need to access. In this case I assume you set up #product in the controller, and it seems that you want a collection #similar_products that you then loop through on the bottom of the page.
We have a product to work with at the beginning. From this, we want products that are in the same categories as the product. So, in your controller do
#similar_products = #product.categories.inject({}) do |result_hash, category|
result_hash[category.name.to_sym] => category.products.reject {|p| p == #product}
result_hash
end
So what we end up with is something that looks like this:
{:category_1 => [product1, product2, product3], :category_2 => [product4,product5]...}
I might recommend limiting the number of products for each category to something like 5 by changing the result_hash assignment to
result_hash[category.name.to_sym] => category.products[0..4]
Now in your view you can loop through the products by category if you like:
<% #similar_products.each do |k,product_array| # remember that the key is the name and the value is an array %>
<% product_array.each do |product| %>
<img src="<%= product.image.path %>" />
<% end %>
<% end %>
I have a nested forms like:
class House < ActiveRecord::Base
has_many :rooms
accepts_nested_attributes_for :rooms
attr_accessible :rooms_attributes
end
class Room < ActiveRecord::Base
has_one :tv
accepts_nested_attributes_for :tv
attr_accessible :tv_attributes
end
class Tv
belongs_to :user
attr_accessible :manufacturer
validates_presence_of :user
end
Now, I want to know for house.id = 1 how many rooms and tvs totally.
In the houses_controller I gave
#houses = House.all
And it's quit simple to get the room count for each house like
<% for house in #houses %>
<%= house.rooms.count %>
<% end -%>
My question is how to get tvs count? I am using this now
<%= house.rooms.map {|room| room.tvs.count}.sum %>
It works, but I am not sure this is good or not.
Is there any better way to get it?
I'd put a method in the model, trying to avoid code in the views.
class House
...
def tvs
rooms.inject(0) {|r, t| t + r.tvs }
end
end
class Room
...
def tvs
tv ? 1 : 0 # it's has_one association right now
end
end
Also, if in your controller your are loading all House's objects, and after that you are going to need the Rooms objects, you should load the houses like:
House.find :all, :include => { :rooms => :tv }
This way you are going to do 1 query, with your approach there will be 1 + N_rooms + N_tvs queries