How to validate the state of a join model before creation - ruby-on-rails

As I got downvoted the first time, this time I try to be as clear as possible about my goals. If they're not clear, please let me know what's missing.
I have course and students which have a has_many through: relationship. When I create a record for a newCourseParticipation, I would like to check if the course is already full (via the full? method).
What is the best way to do that? My first impulse was to introduce a conditional check in the Create action of the controller, now I'm doing the validation in the Course model. But I think it would best be a "before_create" validation in the CourseParticipation model. Not sure how to do this though.
My Course model
class Course < ActiveRecord::Base
has_many :students, through: course_participations
has_many :course_participations
end
And my Student model
class Student < ActiveRecord::Base
has_many :courses, through: course_participations
end
The join model
class CourseParticipation < ActiveRecord::Base
belongs_to :student
belongs_to :course
end
In the UsersController:
def create
#course = Course.find(params[:course_id])
#student = Student.find_or_create_by(user_params)
if #student
#course.participate(#student)
end
end
In the Course model:
def full?
self.students.count >= self.max_students
end
def participate(student)
if !self.full?
course_booking = CourseParticipation.new(course_id: self.id, student_id: student.id)
course_booking.save
else
self.errors.add(:course_full, "course is full")
end
end
Goal:
Best place to validate that course is not full and create an instance of CourseParticipation

Try this:
class CourseParticipation < ActiveRecord::Base
belongs_to :student
belongs_to :course
before_create :check_class_size
private
def check_class_size
!self.course.full?
end
end

Related

How to search in Ruby on Rails

I have the three models:
class Joinedtravel < ApplicationRecord
belongs_to :travel
belongs_to :user
end
class Travel < ApplicationRecord
has_many :joinedtravels
belongs_to :user
end
class User < ApplicationRecord
has_many :joinedtravels
has_many :travels
end
How can I obtain all travels that a user has joined in the past?
I did something like that:
#user = User.find(id)
#past_travels = Travel.where('travels.data < ?', DateTime.now)
#all_joinedtravels = #user.joinedtravels.travels
but i don't kwon how to correctly join the results.
First you need to fix the relationship
class Joinedtravel < ApplicationRecord
belongs_to :travel
belongs_to :user
end
class Travel < ApplicationRecord
has_many :users, through: joinedtravels
has_many :joinedtravels
end
class User < ApplicationRecord
has_many :travels, through: joinedtravels
has_many :joinedtravels
end
Then you can simply search it using
User
.find(id)
.travels
.where('travels.data < ?', DateTime.now)
This should work:
#user = User.find(id)
#past_joinedtravels = #user.joinedtravels.joins(:travels).where('travels.date < ?', DateTime.now)
Try this in the console, and pay attention to the sql produced. That will show you possible errors.
The travelsin the joins clause is the model name. The travelsin the where clause must be the literal database table name, which I just guessed.
Seems to me you'd be better off using a has_and_belongs_to_many relations and a join table to join the User and Travel models as long as you're not including any additional information in the JoinedTravel model?
https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
class Travel < ApplicationRecord
has_and_belongs_to_many :user
end
class User < ApplicationRecord
has_and_belongs_to_many :travels
end
#user = User.find(id)
#past_travels = Travel.where('travels.data < ?', DateTime.now)
#user_travels = #user.travels
You could then see if a user has any travels:
#user.travels.present?

destroy dependent data without destroying original record in rails

Is there an easy way to destroy data associated with a particular record, without destroying the original record. For example, if I have
class User < ActiveRecord::Base
has_many: pets, dependent: :destroy
has_many: houses, dependent: :destroy
end
class Pet < ActiveRecord::Base
belongs_to :user
end
class House < ActiveRecord::Base
belongs_to :user
end
If I wanted to delete a user and all their pets and houses, I could just do something like:
user = User.first
user.destroy
But what if I want to keep the user, but just want to delete their pets and houses? Is there an easy way to do that?
You have to do it manually, with a callback for example.
class User
{callback} :destroy_pets
private
def destroy_pets
self.pets.delete_all
end
end

Define owner after create

Walls belong to users through a WallAssignments association.
class Wall < ApplicationRecord
belongs_to :user
has_many :wall_assignments
has_many :users, :through => :wall_assignments
end
class User < ApplicationRecord
has_many :wall_assignments
has_many :walls, :through => :wall_assignments
end
class WallAssignment < ApplicationRecord
belongs_to :user
belongs_to :wall
end
In the create action, I'm associating the current user with the new wall record.
def create
#wall = Wall.new
#wall.wall_assignments.build(user_id: current_user.id)
if #wall.save
redirect_to #wall
else
redirect_to current_user
end
end
However, aside from allowing many users to belong to the wall, I'd like to have one user (the user who created it) own the wall.
I'm attempting something like this:
class Wall < ApplicationRecord
after_create { owner }
belongs_to :user
has_many :wall_assignments
has_many :users, :through => :wall_assignments
private
def owner
self.owner = Wall.users.first
end
end
Eventually, I'd like to be able to call #wall.owner.name and #wall.owner.id in my views.
I guess you want to have has_many(as users) and has_one(as owner) with same table User.
In this scenario, your Wall model will be:
class Wall < ApplicationRecord
belongs_to :owner, class_name: 'User', foreign_key: :owner_id
has_many :wall_assignments
has_many :users, :through => :wall_assignments
end
You need to add owner_id column in walls table.
So when you create Wall record, it will
class Wall < ApplicationRecord
after_create { add_owner }
private
def add_owner
self.update_column(:owner_id, self.users.first.id) if self.users.present?
end
end
You can also modify controller's create code(I assumed, create method will get called only once.)
def create
#wall = Wall.new(wall_params)
#wall.owner_id = current_user.id
#wall.wall_assignments.build(user_id: current_user.id)
if #wall.save
redirect_to #wall
else
redirect_to current_user
end
end
with this, you don't need to add after_create callback in Wall model.
And then you can call #wall.owner.name and #wall.owner_id

Rails .where statement with nested resources

I'm struggling with a .where statement in an index action.
In my Deals controller, i'd like to list all the deals where the bank of the current_user is participating.
Below are my models :
class User < ActiveRecord::Base
belongs_to :bank
end
class Deal < ActiveRecord::Base
has_many :pools
end
class Pool < ActiveRecord::Base
belongs_to :deal
has_many :participating_banks, dependent: :destroy
has_many :banks, through: :participating_banks
end
class ParticipatingBank < ActiveRecord::Base
belongs_to :pool
belongs_to :bank
end
Here is my Deals Controller Index action :
def index
#deals = Deal.all
end
I don't find any way to say : 'I only want to see a deal if this deal has, at least, one pool where the current_user.bank has been added'.
Any idea?
Many thanks :)
You should do inner join and query joined table for id. You can easily do it in Rails by:
def index
#deals = Deal.joins(pools: :banks).where(banks: { id: current_user.bank_id })
end

Any shortcut for updating join table when creating one of the models

For example, let us say we have
class User < ActiveRecord::Base
has_many :networks, through: user_networks
has_many :user_networks
end
class Network< ActiveRecord::Base
has_many :users, through: user_networks
has_many :user_networks
end
class UserNetwork < ActiveRecord::Base
belongs_to :user
belongs_to :network
end
Is there a shortcut for doing the following in a controller:
#network = Network.create(params[:network])
UserNetwork.create(user_id: current_user.id, network_id: #network.id)
Just curious and I doubt it.
This should work:
current_user.networks.create(params[:network])
But your code implies you are not using strong_parameters, or checking the validation of your objects. Your controller should contain:
def create
#network = current_user.networks.build(network_params)
if #network.save
# good response
else
# bad response
end
end
private
def network_params
params.require(:network).permit(:list, :of, :safe, :attributes)
end

Resources