Ruby on rails one to many relation - ruby-on-rails-3

I have a model User which has one to many relation to the Image model.
How can I limit user to be able to store only 3 images?

How about validation?
class Image
belongs_to :user
validate :max_3_per_user
# (...)
private
def max_3_per_user
if user_id && Image.where(user_id: user_id).count >= 3
errors.add(:images, 'There can only be 3 per user')
end
end
end

Related

Rails ActiveRecord: find related model COUNT with a HABTM (has_and_belongs_to_many) relationship

I have two models
class Items < ApplicationRecord
has_and_belongs_to_many :users
end
class Users < ApplicationRecord
has_and_belongs_to_many :items
end
I want to find out all the items that have 2 users associated with them, and delete them.
I could think of doing it using an iterative approach like:
Item.all.each do |i|
if i.users.all.count == 2
i.delete
end
end
Is there a more elegant way (oneliner?) to do this using only the ActiveRecord ORM ?
Thanks.
Not sure but following should work
Item.joins(:users).group('items.id').having('COUNT(items_users.user_id) = 2').destroy_all
Use group and having for filter the condition.
Item.joins(:users)
.group('COUNT(users.id)')
.having('COUNT(users.id) = ?', 2)
.select('COUNT(users.id) AS users_count')
.destroy_all

RAILS: How to query if there's already a team formed by an array of users

I'm working on an rails API, more specifically in a create operation.
The workflow that I have is this, I have two rails applications, one is an API and the other is an interface. The API manages the different backend operations in order to handle data, store data, and respond in json format to the interface. The interface serves as the frontend, just making http requests to the API in order to display the information.
In my API I have the 3 model listed below:
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
end
class User <ApplicationRecord
has_many :team_users
has_many :teams, through: :team_users
end
class TeamUser < ApplicationRecord
belongs_to :user
belongs_to :team
end
Basically I'm sending an array of user ids from the Interface to the API and I would like to make a query to find out if there's already a team formed by the users (user ids) that I've passed to the API.
I have already tried to do this:
Team.joins(:team_users).where('team_users.user_id' => [3,5])
The problem with this query is that it returns every team that contains the users with id that are equal to 3 or 5. The correct result would be to return a team that has the users 3 and 5 or 5 and 3 as their team members.
Thanks in advance.
Update
The original business rule is this, have an aplication that keeps track of trees, so I have a model named tree and when we create a tree whe must say what team created this tree. So I used a multi select user dropdown field with select2 js library that is how I'm passing the user_ids to the API. So the basic idea is to check is theres already a team composed only by the users passed to the API, if there is already a team I use it's id and say that the tree was registered by that team, if there insn't a team with coposed only buy the users I create a new team and reference it's Id to the tree.
You can approach the problem in different ways. Scrolling over each Team record to check if it contains the associated user_ids is pretty straightforward but inefficient:
team.user_ids.sort == user_ids.sort
But we can make it performant by reversing the process, i.e. iterating over the user_ids to find out corresponding teams, taking their intersection of Team ids and finally checking if any team_id holds those user_ids. This line will return true if there's already a team formed by the users (user ids):
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
You may include it in the User class scope like below:
class User < ApplicationRecord
...
def self.team_exists_for?(user_ids)
# return early
# what should it return when user_ids is empty
# return ??? if user_ids.blank?
# what should it return when any of the id is absent from :users table
# set_of_all_user_ids = Set.new(User.pluck(:id))
# set_of_user_ids = Set.new(user_ids)
# return ??? unless set_of_user_ids.subset? set_of_all_user_ids
# finally
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
end
...
end
Update
So you want to find the team which only has those users provided by user_ids or create a team with them and assign back to the Tree model instance. Combining both approaches described above and defining a scope in the Team model itself seems like a better solution.
class Team < ApplicationRecord
...
def self.find_or_create_for(user_ids)
# find all team_ids which contain user_ids (inclusive)
team_ids = user_ids.map { |id| User.find(id).team_ids }.reduce(:&).flatten
if team_ids.present? # search for the team which ONLY has 'user_ids'
team_id = team_ids.find { |id| Team.find(id).user_ids.sort == user_ids.sort }
end
return Team.find(team_id) if team_id
# or create a team with user_ids and return
team = Team.create! # create a team with required attributes
team.user_ids = user_ids
team
end
...
end
i have implemented this as
add a field key: string on Team and in Team model
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
#callback
before_validation :update_key
def update_key
self.key = Team.key_for_users(self.users)
end
def self.key_for_users(users)
users.sort.map(&:id).join("_")
end
end
so basically after this callback whenever you will create a team there will be a key
for example: -
users = [3,5]
then key in Team will be 3_5
or users = [5,3]
then key in Team will be 5_3
From this we can easily get the result what you wanted
example: -
user_ids = [3,5]
[14] pry(main)> user_ids_simple = user_ids.join('_')
=> "3_5"
[15] pry(main)> user_ids_reverse = user_ids.reverse.join('_')
=> "5_3"
and query will be like this: -
Team.where("key IN (?)",[user_ids_simple, user_ids_reverse])
it may be helpful for you. thanks

How to use after_save callback in child model to update attribute in parent model?

I have two models, User and TrainingSession:
class User < ActiveRecord::Base
has_many :training_sessions
end
class TrainingSession < ActiveRecord::Base
belongs_to :user
end
Each User has a certain number of points based on the sum of the durations of their accumulated training_sessions (in TrainingSession model):
class TrainingSession
def points
sum(:duration) / 6
end
Currently, after a user creates a new training_session, I am updating the points attribute in the User model as follows (from TrainingSession controller):
# POST /training_sessions
# POST /training_sessions.json
def create
#user = User.find_by_id(session[:user_id])
#training_session = #user.training_sessions.new(params[:training_session])
#points = #user.training_sessions.points
#user.update_attribute(:points, #points)
...
The problem is that each time a training_session is created, it doesn't include the points that were just accrued by the user from the most recently created training_session. For example, if a user has 160 points, then they go for a 60 minute run, they will have gained 10 points to reach 170 points. But after creating that 60 minute run training_session, the User model updates it to 160 points. So it is always one step behind, so to speak. I tried using an after_save callback on the TrainingSession model, like so:
class TrainingSession < ActiveRecord::Base
after_save :update_points
private
def update_points
count = #user.training_sessions.sum(:duration)
if count >= 1
points = count / 6
end
#user.update_attribute(:points, points)
end
but then I get this error when trying to create a new training_session:
undefined method `training_sessions' for nil:NilClass
So, my question is, how do I keep up to date the :points attribute on my user model with the inclusion of the most recently created training_session?
#user is undefined in your model. That's why you get the error.
A training session belongs to a user, so in your model you can get the user via self.user or simply user. This code should work:
def update_points
count = self.user.training_sessions.sum(:duration)
points = count / 6 if count >= 1
self.user.update_attribute(:points, points)
end

How could I update an intermediary table in rails using checkobxes?

I am not sure how to figure this one out, because I dont know how I would update my intermediary table, without having to do delete_all. Or where to do inserts and deletes, etc.
I have three classes:
class User < ActiveRecord::Base
has_many :players
has_many :teams, :through => :players
end
class Team < ActiveRecord::Base
has_many :players
end
class Player < ActiveRecord::Base
belongs_to :team
belongs_to :user
(contains other columns like, is_captain, has_paid, etc.)
end
I am displaying a page with checkboxes and you can select which 'users' you would like to be on your team (from your AddressBook). So imagine 25 names listed, and some of them are already checked and others are not. How could I save the data of checkboxes, cause they may have 3 different states: 1) insert brand new player (insert row into player) 2) remove player from team (delete existing player) or 3)do nothing (a player you dont want on your team)
The issue i am running into is that when you 'uncheck' a box, then the front-end does not send that to back-end? Any ideas how this can be done nicely?
Not sure if this is the best way to do this but it works, I am open to recommendations in improving this code
params[:team][:user_ids] ||= []
#team = Team.find(params[:team][:id])
current_players = #team.players
current_players_user_ids = current_players.collect { |player| player.user_id }
user_ids = params[:team][:user_ids]
puts "input ids #{user_ids}"
#add current_user because checkbox does not pass them in since its 'disabled'
user_ids << current_user.id
#delete the current_players that are not in the the received 'checkboxes'
current_players_user_ids.each do |current_player_user_id|
unless user_ids.include?(current_player_user_id)
Player.delete_all(:team_id => #team.id, :user_id => current_player_user_id)
end
end
#iterate through input ids and run 'inserts' on those who are not current-players
user_ids.each do |user_id|
unless current_players_user_ids.include?(user_id)
player = Player.new(:user_id=>user_id, :team_id=>#team.id)
#team.players << player
end
end

ruby validation: limiting number of objects for one object based on time (has_many)

I am programming a booking system.
I want my users to be able to book only one (or a defined number of) resources at a time. However, I do not want to remove "past" reservation for my database, since it will be used for invoicing purposes. On reservation creation, I need to validate that a user has not exceeded its reservation quota, which means has not more than "quota" reservations in the future.
class User < ActiveRecord::Base
has_many :reservations
def active_reservations
#maybe worth to rewrite with a "find"?
my_list = []
reservations.each do |reservation|
if (not reservation.past?)
my_list.push(reservation)
end
return my_list
end
class Reservation < ActiveRecord::Base
belongs_to :user
validate :respect_user_quota
def past?
return (date < Date.now)
def respect_user_quota
if (user.active_reservations.count > user.quota)
errors.add(:user, "User quota exceeded!")
Is this the right way to implement this validation? What could be wrong there (I never see the error message). Should the quota validation be moved to the user class?
I would try and do this more simply and move the validation to user.
class User < ActiveRecord::Base
has_many :reservations
validate :reservation_quota
if sum(reservations.active) > quota # User.quota is implied here
errors.add(:user, "User quota exceeded!")
end
class Reservation < ActiveRecord::Base
belongs_to :user
def active
active? 1 : 0
# If there's a boolean 'active' flag the ? method gets created automatically.
# This could be (reservation_date < Date.now)? ? 1 : 0 for you.
# Using `(expression)? ? true : false` is using the Ternary operator.
end
end