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.
Related
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.
I have nested relationships and built them according to the Rails Guide.
A User has many Collections that have many Sections each containing many Links. When creating a new Link though, the user_id is not being assigned but is always nil. The section_id and collection_id are being set correctly.
Controller
class Api::V1::LinksController < Api::V1::BaseController
acts_as_token_authentication_handler_for User, only: [:create]
def create
#link = Link.new(link_params)
#link.user_id = current_user
authorize #link
if #link.save
render :show, status: :created
else
render_error
end
end
private
def link_params
params.require(:resource).permit(:title, :description, :category, :image, :type, :url, :collection_id, :user_id, :section_id)
end
def render_error
render json: { errors: #resource.errors.full_messages },
status: :unprocessable_entity
end
end
Models
User
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
acts_as_token_authenticatable
has_many :collections, dependent: :destroy
has_many :sections, through: :collections, dependent: :destroy
has_many :links, through: :sections, dependent: :destroy
mount_uploader :image, PhotoUploader
end
Collection
class Collection < ApplicationRecord
belongs_to :user
has_many :sections, dependent: :destroy
has_many :links, through: :sections, dependent: :destroy
mount_uploader :image, PhotoUploader
end
Section
class Section < ApplicationRecord
belongs_to :collection
has_many :links, dependent: :destroy
end
Link
class Link < ApplicationRecord
belongs_to :section
end
Is this the correct way to set up the relationships and can someone help me understand what I am missing?
You can't do
#link.user_id = current_user
You could (instead) do...
#link.user_id = current_user.id
Or more elegantly...
#link.user = current_user
Which assumes you will define the relationship in the model
class Link < ApplicationRecord
belongs_to :section
belongs_to :user
end
But as Andrew Schwartz points out in the comments, it may have been a design mistake to add the field user_id to the links table. You have in the User model has_many :links, through: :sections, dependent: :destroy which does not use any user_id field in the link record. It uses the user_id field in the collections table
Just adding user_id to the links table will NOT mean that link will be returned when you do my_user.links ... it won't be.
Since you're passing a section_id in the link_params that is enough to create the link to the user, so just write a migration to remove the user_id field. If you want to be able to see the associated user from the link, do...
class Link < ApplicationRecord
belongs_to :section
has_one :collection, through: :section
has_one :user, through: :collection
end
and that will let you do my_link.user to retrieve the link's user.
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.
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?
Given that I have the next models:
user.rb
has_many :favorites, dependent: :destroy
has_many :sports, through: :favorites
sport.rb
has_many :favorites, dependent: :destroy
has_many :users, through: :favorites
In the form for create a new user, there is a list of checkboxes of sports, and in the user validation I want to validate that at least one is selected.
I'm doing it like this:
In the user controller create action:
#user = User.new(params[:user])
#user.sports << Sport.find(params[:sports]) unless params[:sports].nil?
if #user.save ...
In the user model
validate :user_must_select_sport
def user_must_select_sport
if sports.empty?
errors.add(:Sport, "You have to select at least 1 sport")
end
end
And it's actually working, but I'm guessing that it has to be a better way of doing this. I'd appreciate any help.
You can use "validates_presence_of"
class User < ActiveRecord::Base
has_many :sports
validates_presence_of :sports
end
But there is a bug with it if you will use accepts_nested_attributes_for with :allow_destroy => true.
You can look into this : Nested models and parent validation