dependent: :destroy doesn't work on has_one relation - ruby-on-rails

If I delete child record so parent record does not get deleted automatically.
class User < ActiveRecord::Base
has_one :agency, dependent: :destroy
accepts_nested_attributes_for :agency
end
class Agency < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
if #agency.present?
#agency.user.destroy
flash[:notice] = 'Agency Deleted'
end
Destroy child record so parent record automatically destroy.

I think, your models could be re-written like this to achieve expected output.
class User < ActiveRecord::Base
has_one :agency # Change
accepts_nested_attributes_for :agency
end
class Agency < ActiveRecord::Base
belongs_to :user, dependent: :destroy # Change
accepts_nested_attributes_for :user
end
if #agency.present?
#agency.destroy # Change
flash[:notice] = 'Agency Deleted'
end
Let's think logically now.
What have you changed is, you made User dependent on Agency and now it's rails doable to form a parent-child relationship to get accepted output. So when you destroy an #agency, it will also delete the dependent user record.

You should use the following code to delete a user and its associated agency without making any change to your model.
class User < ActiveRecord::Base
has_one :agency, dependent: :destroy
accepts_nested_attributes_for :agency
end
class Agency < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
if #agency.present?
user = #agency.user #Change
user.destroy # This will destroy both user and associated agency.
flash[:notice] = 'Agency and User Deleted'
end
A complete official guide on dependent: :destroy can be find here.

Related

Button to connect an association in Rails is not working. Transfer of ownership

I am working my first Rails project, an adoption app and trying to bridge an association to a new potential owner in Rails. My controller action is moving through my adoption_request method, but no changes are being persisted to my join table in ActiveRecord. Can someone please tell me what I am missing here?
The app:
Owners sign up or log in to their account. They can add their Ferret using a form. Later, the Owner may want to create an Opportunity listing to adopt/rehome their animal. People browsing should be able to click on an Opportunity they are interested in, which should establish an association in the join table Opportunity, :adopter_id.
My Models:
class Owner < ApplicationRecord
has_secure_password
has_many :ferrets, dependent: :destroy
has_many :opportunities, dependent: :destroy
has_many :ferret_adoptions, through: :opportunities, source: :ferret
accepts_nested_attributes_for :ferrets, :opportunities
end
class Ferret < ApplicationRecord
belongs_to :owner
has_many :opportunities
has_many :owners, through: :opportunities
end
class Opportunity < ApplicationRecord
belongs_to :ferret
belongs_to :owner
end
In Opportunities Controller, my adoption_request method:
def adoption_request
#owner = Owner.find(session[:owner_id])
#opportunity = Opportunity.find(params[:id])
#opportunity.adopter_id = [] << current_user.id
current_user.req_id = [] << #opportunity.id
flash[:message] = "Adoption request submitted."
redirect_to questions_path
end
I am using a button to do this, but I am open to change that if something may work better:
<button><%= link_to 'Adoption Request', adoption_request_path, method: :post %> <i class='fas fa-heart' style='color:crimson'></i></button>
As an Owner when I click the button to make an Adoption Request, I am seeing all the working parts in byebug, and I am being redirected to the next page with the success message as if everything worked, but there is no Association actually being persisted to the database.
I appreciate any feedback you can offer.
I'm assuming here that Opportunity should represent something like a listing (it needs a less vague name).
If so you're missing a model and its table if ever want more then one user to be able to respond to an Opportunity:
class Owner < ApplicationRecord
has_secure_password
has_many :ferrets, dependent: :destroy
has_many :opportunities, dependent: :destroy
has_many :adoption_requests_as_adopter,
foreign_key: :adopter_id,
class_name: 'AdoptionRequest'
has_many :adoption_requests_as_owner,
through: :opportunities,
source: :adoption_requests
accepts_nested_attributes_for :ferrets, :opportunities
end
class Ferret < ApplicationRecord
belongs_to :owner
has_many :opportunities
has_many :owners, through: :opportunities
has_many :adoption_requests, through: :opportunities
end
class Opportunity < ApplicationRecord
belongs_to :ferret
belongs_to :owner
has_many :adoption_requests
end
class AdoptionRequest < ApplicationRecord
belongs_to :adopter, class_name: 'Owner' # ???
belongs_to :opportunity
has_one :ferret, through: :opportunity
has_one :owner, through: :opportunity
end
If you just have a adopter_id on your opportunities table it can only ever hold a single value.
I would just set the route / controller up as a normal CRUD controller for a nested resource:
# routes.rb
resources :opportunities do
resources :adoption_requests, only: [:create, :index]
end
<%= button_to "Adopt this ferret", opportunity_adoption_requests_path(#opportunity), method: :post %>
class AdoptionRequestsController < ApplicationController
before_action :set_opportunity
# #todo authorize so that it can only be viewed by the owner
# GET /opportunities/1/adoption_requests
def index
#adoption_requests = #opportunity.adoption_requests
end
# #todo authorize so that a current owner can't create adoption_requests
# for their own ferrets
# POST /opportunities/1/adoption_requests
def create
#adoption_request = #opportunity.adoption_requests.new(
adopter: current_user
)
if #adoption_request.save
redirect_to #opportunity, notice: 'Thank you for your reply! The owner of the ferret will be notified.'
# #todo send notification to owner
else
redirect_to #opportunity, notice: 'Oh noes!'
end
end
private
def set_opportunity
#opportunity = Opportunity.find(params[:opportunity_id])
end
end
Its only later when the owner actually accepts a adoption_request that you will actually update the opportunity and this is a seperate question for a later time.

How do I like a user model with this setup?

I have setup a polymorphic liking in my my app where a user can like other models e.g book, chapter, article... Now I tried to take it a little further by allowing a user like another user but I'm running to this error:
Validation failed: User must exist
pointing to
likes.where(item: item).create!
This is my initial setup for liking other models excluding the user model
like.rb
belongs_to :user
belongs_to :item, polymorphic: true
user.rb
has_many :likes, dependent: :destroy
def toggle_like!(item)
if like = likes.where(item: item).first
like.destroy
else
likes.where(item: item).create!
end
end
def likes?(item)
likes.where(item: item).exists?
end
likes_controller.rb
class LikesController < ApplicationController
before_action :authenticate_user!
def toggle
if params[:book_id]
item = Book.friendly.find(params[:book_id])
elsif params[:user_id]
item = User.find_by(username: params[:username])
end
current_user.toggle_like!(item)
redirect_back(fallback_location: root_path)
end
end
book.rb
has_many :likes, as: :item, dependent: :destroy
For a user to like another user, i adjusted the user.rb from
has_many :likes
to
has_many :likes, as: :item, dependent: :destroy
This is when I get the error
Validation failed: User must exist
pointing to
likes.where(item: item).create!
in the user.rb
Keep has_many :likes, dependent: :destroy and also add has_many :received_likes, as: :item, class_name: "Like", dependent: :destroy. I think that will fix it. Because when you put User has_many: :likes, as: :item, and removed User has_many: :likes, this means that the association Like belongs_to :user was one-sided.

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

How to use build method with a has_many :through association

My Models:
class Vip < ActiveRecord::Base
belongs_to :organization
has_many :events
has_many :organizations, :through => :events
end
class Organization < ActiveRecord::Base
belongs_to :user
has_many :events
has_many :vips, :through => :events
end
class Event < ActiveRecord::Base
belongs_to :organization
belongs_to :vip
end
My vips Controller:
def create
#organization = Organization.find(params[:organization_id])
#vip = #organization.vips.build(vip_params)
if #vip.save
redirect_to organization_path(#organization)
else
render 'new'
end
end
def vip_params
params.require(:vip).permit(:name, :about, :organization_id)
end
Before I started using the has_many :through associations, the build method would automatically add the foreign key to the new vip. So my vips table would have the organization_id column populated. Since using the has_many associations, the organization_id column is being left NULL on 'vip#create'.
Is there a reason that build wouldn't work the same way anymore with my new associations?

How to delete nested objects in Rails3?

How can I delete nested objects in a form? I found out that I need to add :allow_destroy in the parent model at the accepts_nested_attributes_for directive.
Further, I want to restrict the deletion. A nested object only should be deleted, if the parent object is the only one that retains the association.
Example:
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships
end
Explanation: A company can host many internships. Therefore, I do not want to delete the company record as long as there is at least one other internship associated with it.
You could use dependent => :destroy
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships, :dependent => :destroy
end
If you return false in a before_destroy filter, then the destroy action will be blocked. So we can check to see if there are any internships associated to the company, and block it if so. This is done in the company model.
class Company < ActiveRecord::Base
has_many :internships
before_destroy :ensure_no_internships
private
def ensure_no_internships
return false if self.internships.count > 0
end
end

Resources