Set rooms for users - ruby-on-rails

I'm trying to resolve a problem but I don't find the solution. This is my code:
class User < AR::Base
belongs_to :room
end
class Room < AR::Base
has_many :users
end
class SetupRooms
def initialize
#users = User.all
#rooms = Room.all
#room_max_users = #users.size / #rooms.size
end
def setup
groups = #users.in_groups_of(#room_max_users)
# Now, How Can I fill rooms with users?
end
end
Thanks

def setup
groups = #users.in_groups_of(#room_max_users)
#rooms.zip(groups).each do |room, group| # now "room" is a Room and "group" is an Array of Users
group.delete_if { |user| user.nil? } # in_groups_of pads with nil if there are leftover spaces
room.users = group
end
end

You do not need the initialize method. Your setup can be written like this
def setup
Room.all.each_with_index do |room, i|
room.users = User.find(:all, :limit => room_max_users + i + 1)
room.save
end
end
So this fills your rooms with users, based on their id in the database. Only the needed users are loaded at once so it should not be performance critical.
btw this method should be defined as a class method, perhaps on Room so you could invoke it like
class Room < AR::Base
has_many :users
def self.fill_with_users
Room.all.each_with_index do |room, i|
room.users = User.find(:all, :limit => room_max_users + i + 1)
room.save
end
end
end
Room.fill_with_users
In this way you won't need your setup class as well.

Related

Caching association that was in where clause

Let me show an example:
I have 2 models:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
scope :created_in, ->(start_date, end_date) { where(created_at: start_date..end_date) }
end
What I want is to get users that created post during a specific period:
users = User.includes(:posts).joins(:posts).merge(Post.created_in(start_date, end_date))
Is it somehow possible to cache posts that are in the where clause? So after I do
users.first.posts
it will show me exactly those posts that match the condition without producing any additional queries.
No, I don't think this is possible. Depending on the context, what you can do is to do a lookup table which you memoize / cache. Something like
User.all.each do |user|
posts = posts_by_user_id[user.id]
end
def posts_by_user_id
#_posts_by_user_id ||= posts.group_by(&:user_id)
end
def posts
Post.created_in(start_date, end_date)
end

How do I effectively preload conditional associations in Rails?

I have the following two models:
class Company < ActiveRecord::Base
has_many :employees # A company can have 1000's of employees.
end
class Employee < ActiveRecord::Base
belongs_to :company
end
One of my use cases looks like this
In the controller:
def my_action
#companies = Company.where(country: 'GB').preload(:employees)
#title = params[:title] # E.g. 'CTO'
end
In the view:
- #companies.each do |c|
= c.employees.where(title: #title).first.last_name
The problem with the above is that it will create an N+1 in the view
Now I could change the view to:
# In the view
- #companies.each do |c|
= c.employees.select{|e| e.title == #title}.first.last_name
This will solve the N+1 but it will still load out potentially 1000's of employee records even though I don't need them (I only need the ones with the right title).
I could solve that by:
class Company < ActiveRecord::Base
has_many :employees
has_many :ctos, ->{ where(title: 'CTO') }
end
# In the controller
def my_action
#companies = Company.where(country: 'GB').preload(:ctos)
end
# In the view
- #companies.each do |c|
= c.ctos.first.last_name
The problem with this however is that it would require me to add associations for every single possible type on Company.
So the question is, how can I solve this?
companies = Company.where(country: 'GB')
title = params[:title]
#company_employee = Employee.where(company: companies, title: title).group_by(&:company)
#company_employee will be hash with structure { company => [employees] }

How to get many sum of child columns?

I have two tables, Member and MemberRecord.
This are their relationship:
# Member Model
class Member < ActiveRecord::Base
has_many :member_records, :dependent => :destroy
end
# MemberRecord Model
class MemberRecord < ActiveRecord::Base
belongs_to :member
end
In MemberRecord There are many columns: two_pointer_attempt, two_pointer_made, three_pointer_attempt, three_pointer_made, free_throws_attempt, free_throws_made, offensive_rebound, defensive_rebound, assist, block, steal, turnover, foul, score
Can I get those columns sum in more efficient way?
This is what I did so far:
class Member < ActiveRecord::Base
belongs_to :team
has_many :member_records, :dependent => :destroy
validates :name, :number, presence: true
validates_uniqueness_of :name, scope: :team_id
validates_inclusion_of :number, in: 0..99
def sum_two_pointer_made
self.member_records.sum(:two_pointer_made)
end
def sum_two_pointer_attempt
self.member_records.sum(:two_pointer_attempt)
end
def sum_two_pointer_total
sum_two_pointer_made + sum_two_pointer_attempt
end
def sum_three_pointer_made
self.member_records.sum(:three_pointer_made)
end
def sum_three_pointer_attempt
self.member_records.sum(:three_pointer_attempt)
end
def sum_three_pointer_total
sum_three_pointer_made + sum_three_pointer_attempt
end
def sum_free_throws_made
self.member_records.sum(:free_throws_made)
end
def sum_free_throws_attempt
self.member_records.sum(:free_throws_attempt)
end
def sum_free_throws_total
sum_free_throws_made + sum_free_throws_attempt
end
def sum_offensive_rebound
self.member_records.sum(:offensive_rebound)
end
def sum_defensive_rebound
self.member_records.sum(:defensive_rebound)
end
def sum_assist
self.member_records.sum(:assist)
end
def sum_block
self.member_records.sum(:block)
end
def sum_steal
self.member_records.sum(:steal)
end
def sum_turnover
self.member_records.sum(:turnover)
end
def sum_foul
self.member_records.sum(:foul)
end
def sum_score
self.member_records.sum(:score)
end
end
I will give you an example with two columns and you can extend it for your number of columns.
class Member < ActiveRecord::Base
# add associations here as already present
MR_SUM_COLUMNS = %w{
assist
block
} # add more member record columns here
MR_SUM_COLUMNS.each do |column|
define_method "member_record_#{column}_sum" do
member_record_sums.send(column)
end
end
private
def member_record_sums
#_member_record_sums ||=
begin
tn = MemberRecord.table_name
sums_str =
MR_SUM_COLUMNS.map do |c|
"SUM(#{tn}.#{c}) AS #{c}"
end.join(', ')
self.member_records.select(sums_str).first
end
end
end
m = Member.first
s1 = m.member_record_assist_sum
s2 = m.member_record_block_sum
Explanation:
In ActiveRecord's select method, you can store the sum of a column as a particular value. For example:
# you have only two members with ids 1 and 2
m = Member.select("SUM(id) AS id_sum").first
m.id_sum #=> 3
So we're storing all sums of member_records in one go: in the member_record_sums method. We are also using an instance variable to store the results so that subsequent calls to the method do not query the database.
From there, all we have to do is define our sum-lookup methods dynamically.

How to iterate through array in controller

I am making a Twitter clone using rails 4 just for practice. When a user is logged in, on the timeline I only want to display tweets of the people they follow (friends) and their own tweets in DESC order. I'm using tweets#index as my timeline. Currently I am displaying all tweets in the database to the user:
def index
#tweets = Tweet.all.order("created_at DESC")
end
I added an instance variable called #user_friendships that contains the current logged in users friends, witch I can then iterate through and access their tweets. Here is what it now looks like:
def index
#user_friendships = current_user.user_friendships.all
#tweets = Tweet.all.order("created_at DESC")
end
I don't want to loop through user_friendships and grab it's friend and then tweets in the view as well as loop through the current_users tweets.
In the controller, I want to have one instance variable that contains the tweets of both the current_user and each friends tweets in user_friendships...so in the View I only have to iterate through the new array.
Model Code
### User.rb
has_many :tweets
has_many :user_friendships
has_many :friends, through: :user_friendships
acts_as_voter
def to_param
username
end
### user_friendship.rb
belongs_to :user
belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
### tweet.rb
belongs_to :user
def index
#tweets = current_user.related_tweets.sort_by{|t| t.created_at}.reverse!
end
in model User
class User < ActiveRecord::Base
# ...
def related_tweets
friends_tweets = friends.map{|f| f.tweets}
return (tweets.to_a + friends_tweets).flatten
end
# ...
end
or other solution if you dont want to work with arrays
def index
#tweets = Tweet.for_user(current_user).order(cretead_at: :desc)
end
in model Tweet
class Tweet < ActiveRecord::Base
# ...
def self.for_user user
me_and_friends = [user.id] + user.friends.map(&:id)
where(:user_id => me_and_friends)
end
# ...
end
You didn't show the relations you have between tweets and users, but I assume you've got user has many tweets. Then, you can do something like this:
#tweets = Tweet.where(user_id: [current_user.id].concat(current_user.user_friendships.all.map(&:id)))
You can do something like this:
def index
#tweet_wall = Tweet.all.where(user_id: current_user.id).where(relationship between user and friend)
end
Second condition would depend on your model.

Better recursive loop in Ruby on Rails

Using Rails 3.2. Let's say I want 2 options:
Get all trip photos.
Get the first trip photo.
I have the following code:
# trip.rb
class Trip < ActiveRecord::Base
has_many :trip_days
def trip_photos
if (photos = trip_days.map(&:spots).flatten.map(&:photos).flatten.map)
photos.each do |photo|
photo.url(:picture_preview)
end
end
end
def trip_photo
trip_photos.first
end
end
# trip_day.rb
class TripDay < ActiveRecord::Base
belongs_to :trip
has_many :trip_day_spots
has_many :spots, :through => :trip_day_spots
end
# trip_day_spot.rb
class TripDaySpot < ActiveRecord::Base
belongs_to :trip_day
belongs_to :spot
end
#spot.rb
class Spot < ActiveRecord::Base
end
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:page => params[:page], :per_page => 25)
end
end
As expected, the trip_photos method generates lots of SQL query. I wonder if there is any better way to do it?
It is because of N+1 queries. In this cases, we need to eager load all the associations of base object, so that when ever you call its associated object, it wont fire any queries for fetching them, simply it will get them from its cached object.
Hope this will work, but not tested. I assumed and wrote the following query.
def trip_photos
user_trip_days = trip_days.includes(:spots => :photos)
photos = user_trip_days.collect {|trip_day| trip_day.spots.map(&:photos).flatten}.flatten
photos.each do |photo|
photo.url(:picture_preview)
end if photos
end
Let me know if you get any errors.
For more info on eager loading associated objects in ActiveRecord, go through
Guides for Rails and Rails cast and Rails Tips
This might not be the most rails-y way, but if you truly wanted to get all the spots in one hit you could do something like:
def spots
Spot.joins("join trip_days_spots on spots.id = trip_days_spots.spot_id join trip_days on trip_days.id = trip_days_spots.trip_day_id join trips on trips.id = trip_days.trip_id").where("trips.id = ?", self.id)
end
then change your loop to:
def trip_photos
spots.map(&:photos).flatten.each do |photo|
photo.url(:picture_preview)
end
end
The code works fine, but to eager load, just add :include:
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:include => [:trip_days => [:spots => :photos]], :page => params[:page], :per_page => 25)
end
end

Resources