Method each not working for getting related model - ruby-on-rails

I write:
<%= Chat.find_by(id: 6).chatusers.each { |chat_user| chat_user.user.inspect } %>
(There are three models: Chat, Chatuser, User)
Chat.find_by(id: 6).chatusers returns collection of table "chats_users" and this is fine working.
But i can not get model user by each element of collection in method "each".
So, question is why each not working to get relative model "user" or how to resolve my task by each or another way.
class Chat < ActiveRecord::Base
has_many :chatusers
has_many :users, through: :chatusers
class Chatuser < ActiveRecord::Base
self.table_name = "chats_users"
belongs_to :chat
belongs_to :user
validates :chat_id, presence: true
validates :user_id, presence: true
end
class User < ActiveRecord::Base
has_many :chats, class_name: "Chatuser"
Thanks.
Resolving
Solution is using another style of ruby-html writing:
<% Chat.find_by(id: 6).chatusers.each do |chat_user| %>
<%= chat_user.user.inspect %>
<% end %>
Thanks for Ruby on Rails Talk

If i understood you question right, and you want to get all users for a chat, then this would do it:
Chat.find(6).users

Resolving
Solution is using another style of ruby-html writing:
<% Chat.find_by(id: 6).chatusers.each do |chat_user| %>
<%= chat_user.user.inspect %>
<% end %>
Thanks for Ruby on Rails Talk

Related

Ruby on rails, how to query a database for objects without an association?

This is my first stack overflow question, im very excited to be here! I just started coding this year so I am still a rookie and need your assistance with rails.
I am creating a web app that simulates a hockey draft pool. When a user creates a league, a bunch of player projections get automatically created and displayed. The user can then create teams within the league and assign projections to the teams. I created a model called player which is a way to associate a projection to a team. When the user adds a projection to a team they are actually creating an instance of a player that is associated to both. Is there any other way to do this having this player model?
One issue im having right now is how long it takes to query the database each time I display the available projections. Once a projection has a child player it should should no longer show up in the projection list. Here is the code that loops through all the projections and only displays ones without a child player.
<h3>Projections</h3>
<ol>
<% #projections.each do |projection| %>
<% unless projection.player %>
<li><%= projection.player_name %><%= link_to "Assign to Team", new_player_path(leauge: #leauge, projection: projection.id) %></li>
<% end %>
<% end %>
</ol>
Since there are 600 projections this takes 10-30s to query the database everytime the page is loaded. Is there a more effective way to do this?
Here are my models:
class Leauge < ApplicationRecord
require 'csv'
belongs_to :user
has_many :teams
has_many :projections
validates :name, uniqueness: true
validates :name, presence: true
def create_projections
CSV.foreach(Rails.root.join('lib/seeds/projections.csv'), headers: true, header_converters: :symbol, encoding: 'ISO-8859-1') do |row|
data = row.to_hash
Projection.create!(
player_name: data[:player],
player_team: data[:team],
goals: data[:goals],
assists: data[:assists],
points: data[:points],
pp_points: data[:pp_points],
hits: data[:hits],
blocks: data[:blk],
pim: data[:pim],
fow: data[:fow],
sog: data[:sog],
leauge: self
)
end
end
end
class Player < ApplicationRecord
belongs_to :projection
belongs_to :team
end
class Projection < ApplicationRecord
belongs_to :leauge
has_one :player
has_one :team, :through => :player
end
class Team < ApplicationRecord
belongs_to :leauge
has_many :players
has_many :projections, :through => :players
validates :name, uniqueness: true
validates :name, presence: true
end
THANKS

ActiveAdmin / Formtastic sortable has_many through relationship

I may be missing something fundamental here, but I can't seem to get ActiveAdmin to work with a sortable has_many through relationship, with the ability to create new records.
So given the following models
class User < ActiveRecord::Base
has_many :user_videos
has_many :videos, through: :user_videos
accepts_nested_attributes_for :user_videos
accepts_nested_attributes_for :videos
...
end
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
accepts_nested_attributes_for :video
end
class Video < ActiveRecord::Base
has_many :user_videos
has_many :users, through: :user_videos
...
end
(I admit I'm throwing accepts_nested_attributes_for around somewhat in the hopes that something may work)
And Active Admin setup goes something like this (WIP of course):
f.inputs "User" do
f.has_many :user_videos, heading: 'Videos', sortable: :order, allow_destroy: true, new_record: 'New Record' do |v|
v.inputs for: :video do |video|
video.input :video_url
end
end
f.has_many :videos, heading: 'Videos', new_record: 'New Video' do |v|
v.input :video_url
end
end
f.actions
The first has_many on the :user_videos association does not seem to render any inputs. If there are records there, I can see that video.input :video_url is actually returning an li tag with label and input, however nothing gets rendered to the page. For new records the whole v.inputs bit does not get run (do I need to create the child records somehow there first?).
The second has_many will work in that you'll be able to add records, and update existing records, however it's impossible to sort as the order column is on the UserVideos model. I include this more as illustration than anything.
If anyone has any pointers for this, they would be most appreciated. :)
WHOA! I know I am late to the party, but this is a perfect opportunity to utilize the :delegate method!
Your UserVideo class would look something like this
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates_with VideoValidator
delegate :video_url, :video_url=, to: :video
end
Best of luck!
Since nobody seemed interested in tackling this, I took another approach - rather than trying to get ActiveAdmin / Formtastic to work with the existing model structure, I added getters and setters for the necessary field on the intersection model.
class UserVideo < ActiveRecord::Base
belongs_to :user
belongs_to :video
validates_with VideoValidator
def video_url
self.video = Video.create if video.nil?
self.video.video_url
end
def video_url=(video_url)
self.video = Video.create if video.nil?
self.video.video_url = video_url
# Video url is set via Active Admin, AA will not call save on the video as it does not realise it's changed
self.video.save! if video.present? and video.valid?
end
end
Doing this meant that Active Admin did not need to know about the Video model, and could just operate on the UserVideo model:
f.has_many :user_videos, heading: 'Videos', sortable: :order, allow_destroy: true, new_record: 'New Record' do |v|
v.input :video_url, :hint => (v.object.video.embed_code unless v.object.nil? or v.object.video.nil?)
end
If anyone has an actual solution rather than a work around, I'd love to hear it, but otherwise this is a possible solution for anyone searching for an answer to the same problem.

how to obtain a subset of records for a grouped_collection_select in Rails

I'm trying to obtain a subset of records for a grouped_collection_select, in the schema I have a Brand and a Model, and we handle different lines of products, when we request a product, we specify the line that we are going to request, so, we can have the Brands, and the models of that line, so the models are defined as follows:
Model for Brand
class Brand < ActiveRecord::Base
validates :brand_key, uniqueness: true, presence:true
has_many :models
accepts_nested_attributes_for :models, allow_destroy: true
end
Model for Modelx
class Modelx < ActiveRecord::Base
belongs_to :brand
belongs_to :line
validates :model_key, uniqueness: true, presence:true
end
Model for Line
class Line < ActiveRecord::Base
validates :line_key, uniqueness: true, presence:true, length: {in: 1..6}
has_many :line_features, -> { order(:sort_order) }
has_many :modelx
accepts_nested_attributes_for :line_features, allow_destroy: true
end
Model for Product
class Product < ActiveRecord::Base
belongs_to :vendor
belongs_to :line
belongs_to :brand
belongs_to :modelx
belongs_to :operating_status
has_many :line_features, -> { order(:sort_order)}
store_accessor :line_features
end
Controller for Product (new)
def new
#brands=brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id])
...
end
excerpt from partial form
<%= f.collection_select( :brand_id, #brands, :id, :brand,{:prompt =>'Brands'}) %>
<%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %>
Now, the problem that I'm facing is that when I display a model I need to present only the models available for that brand and line, but, the request is bringing all the models for the brand as it's supposed to be and I don't know how to discriminate those lines that are not needed.
Any help, hint or suggestion is highly appreciated.
Update to question
I don't know if this is a workaround or a solution to the question, but, it was the only way that I found to get to a solution for the requirement, for that reason I'm posting as an update instead of answering the question for whom it may help.
Reviewed the documentation once again and find out that the method :modelx referred by <%= f.grouped_collection_select(:modelx_id, #brands, :modelx, :brand, :id, :modelox) %> was requesting all the models as noted in apidock, this method was the solution for the problem.
Created a method in the brand model according to the subset depicted above, due to the fact that the grouping was made by brand, here the excerpt
Model for Brand (Excerpt)
...
def modelx_ltvi
Modelx.where("line_id = ? and brand_id =?",$line_id, self.id)
end
Special Note: Due to my inexperience, I was not able to pass the value of the :line_id from the form, so, I put it in a global variable.
Modified the form partial
Excerpt from partial form
...
<% $line_id=#product.line_id %>
...
<%= f.grouped_collection_select(:modelx_id,
#brands, :modelx_ltvi, :brand,
:id, :modelx) %>
<% end %>
And that makes the hamster run.
Filter your models by the brands you just found, something like:
#brands=Brand.where(id: Modelx.select(:brand_id).where(line_id: params[:line_id]))
#modelxs=Modelx.where(line_id: params[:line_id], brand_id: #brands.map(&:id))
#selected_model_names = #modelxs.map(&:name).sort.uniq

Rails, how to avoid the "N + 1" queries for the totals (count, size, counter_cache) in associations?

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

Display number of items per category with .count rails

I'm trying to display the number of photos a category has in a list.
For it, I'm trying to do in my view:
<%= #photos.zone.name("Zone1").count%>
<%= #photos.zone.name("Zone2").count%>
<%= #photos.zone.name("Zone3").count%>
<%= #photos.zone.name("Zone4").count%>
But this doesn't work, and I don't know if this will make a million requests to my ddbb.
Which is the correct way to do this? Making a scope for each category?
Update
class Photo < ActiveRecord::Base
# validate :validate_minimum_image_size
has_many :tags , dependent: :destroy
belongs_to :user
belongs_to :category
belongs_to :zone
validates_presence_of :title, :description, :category, :zone
acts_as_votable
is_impressionable
before_destroy { |record| record.tags.destroy_all if record.tags.any? }
class User < ActiveRecord::Base
rolify
has_many :photos
has_many :tags, through: :photos
Thanks
This line here is wrong because you are calling it on a collection, and it doesn't represent your data model correctly.
<%= #photos.zone.name("Zone1").count%>
Let's say you wanted to get the zone of each photo, you would do something like this:
<% #photos.each do |photo| %>
<%= photo.zone.count %>
<% end %>
This still does not make sense because your association states that a photo belongs to a zone. So a photo can only have one zone per your data model.
class Photo
# ...
belongs_to :zone
end
Based on this information, I will assume that you want to display zones, and the number of photos per zone. In which case you would do something like this:
<%= #zone.photos.count %>
Or, if you wanted to show multiple zones on the same page:
<% #zones.each do |zone| %>
<%= zone.photos.count %>
<% end %>
How would you prepare the data? In your controller you would do something like this:
#zone = Zone.includes(:photos).find(params[:id]) # assuming a /zones/:id path
Or for multiple zones:
#zones = Zone.all.includes(:photos) # assuming a /zones/ path
It is also possible that you want to display photos grouped by zone, which is another story.
Extending #Mohamad's answer
The best bet is to use counter_cache.In your Photo model set counter_cahe =>true for zone
class Photo < ActiveRecord::Base
# validate :validate_minimum_image_size
has_many :tags , dependent: :destroy
belongs_to :user
belongs_to :category
belongs_to :zone,:counter_cache => true #here
validates_presence_of :title, :description, :category, :zone
acts_as_votable
is_impressionable
before_destroy { |record| record.tags.destroy_all if record.tags.any? }
end
Then add a column photo_counts to your zones table and use it like this
<% #zones.each do |zone| %>
<%= zone.photo_counts %>
<%end%>
This avoids multiple requests to the DB.Look into this Railscast for more info.
Hope it helps!
simply
<%= #photos.select{|photo| photo.zone.name == "Zone1"}.count%>

Resources