I am trying to implement likes into my rails app.
like model
class Like < ApplicationRecord
belongs_to :comment
belongs_to :post
belongs_to :user
validates :user_id, uniqueness: {scope: :post_id}
validates :user_id, uniqueness: {scope: :comment_id}
end
user model
class User < ApplicationRecord
has_secure_password
has_one :profile, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :posts, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liked_comments, :through => :likes, :source => :comment, dependent: :destroy
has_many :liked_posts, :through => :likes, :source => :post, dependent: :destroy
has_one_attached :avatar
validates :username, presence: true, uniqueness: true
validates :email, presence: true, uniqueness: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, length: { minimum: 8 }
validates :password, format: { with: /\A[a-zA-Z0-9!##$%^&*()_]+\z/ }
# validates :password, confirmation: true
# validates :password_confirmation, presence: true
end
post & comment model
class Post < ApplicationRecord
belongs_to :category
belongs_to :user
has_many :comments, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :liking_users, :through => :likes, :source => :user
has_many_attached :images
def liked?(user)
!!self.likes.find{|like| like.user_id == user.id}
end
end
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
has_many :likes, dependent: :destroy
has_many :liking_users, :through => :likes, :source => :user
has_many_attached :images
def liked?(user)
!!self.likes.find{|like| like.user_id == user.id}
end
end
The issue I'm having is when I run my seed data I get one of two errors NoMethodError: undefined method "marked_for_destruction?" for false:FalseClass if set up like so:
#like0 = Like.create!(
user: #admin0,
post: #post3 & #post5 & #post6 & #post8 & #post9,
comment: #comment1,
username: #admin0.username
)
or NotNullViolation: ERROR: null value in column "comment_id" violates not-null constraint if set up like so:
class Like < ApplicationRecord
belongs_to :comment, optional: true
belongs_to :post, optional: true
belongs_to :user
validates :user_id, uniqueness: {scope: :post_id}
validates :user_id, uniqueness: {scope: :comment_id}
end
#like0 = Like.create!(
user: #admin0,
post: #post3,
comment: #comment1,
username: #admin0.username
)
my question is how can I better set up the relationship between these 4 models so as to not get these errors?
Related
I have 3 models user, micropost, like
I followed Ruby on Rails Tutorial by Michael Hartl
https://rails-4-0.railstutorial.org/book
Now I am adding new features and was trying to add a like button and after some changes now my micropost delete button doesn't work.
#micropost_controller
def destroy
Micropost.find(params[:id]).destroy
redirect_to root_url
end
#_micropost.html.erb
<%= link_to "delete", micropost, method: :delete,
data: { confirm: "You sure?" },
title: micropost.content %>
Even though i am calling Micropost.find Active Record searches in likes table and gives the error as
ActiveRecord::StatementInvalid in MicropostsController#destroy
SQLite3::SQLException: no such column: likes.micropost_id: SELECT "likes".* FROM "likes" WHERE "likes"."micropost_id" = ?
Similar thing happens when i try to delete a user. So basically whichever destroy i am trying to call it is redirected to like model
PS: the problem started after i executed
ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")
as i was getting BusyException: database is locked: commit transaction
models
#like.rb
class Like < ApplicationRecord
belongs_to :microposts, optional: true
belongs_to :users, optional: true
validates :src_user_id, presence: true
validates :des_user_id, presence: true
validates :post_id, presence: true
end
#micropost.rb
class Micropost < ActiveRecord::Base
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :content, presence: true, length: { maximum: 140 }
validates :user_id, presence: true
has_many :likes, dependent: :destroy
# Returns microposts from the users being followed by the given user.
def self.from_users_followed_by(user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids}) OR user_id = :user_id",
user_id: user.id)
end
def self.search(search)
where("content LIKE ?", "%#{search}%")
end
end
#user.rb
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
has_many :likes, foreign_key: "src_user_id", dependent: :destroy
has_many :liked_by_users, through: :likes, source: :likes
scope :starts_with, -> (name) { where("name like ?", "#{Example User}%")}
before_save { self.email = email.downcase }
before_create :create_remember_token
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, unless: Proc.new { |user| user.password.nil? }
validates :password_confirmation, presence: true, unless: Proc.new { |user| user.password.nil? }
validates :birthday, presence: true
validate :email_verification, on: :update
end
class User
has_many :microposts, dependent: :destroy
# You never have to specify foreign key for indirect assocations
has_many :likes, through: :microposts
end
class Micropost
belongs_to :user
has_many :likes, dependent: :destroy, foreign_key: "post_id"
end
class Like
# You must specify the foreign key if it cannot be derived from the name of the association
# `belongs_to :micropost` will use micropost_id
belongs_to :micropost, optional: true, foreign_key: "post_id"
belongs_to :user # you have to add a `likes.user_id` column
end
Although I would really question if you want to use the same table/model for user likes and micropost likes. It really just makes things a lot more complicated and you end up with a table with a large number of null values. If this is part of the book I really wonder what the author was thinking as its a really bad design.
You can just set it up as:
# app/models/users/like.rb
# table name is users_likes
module Users
class Like
belongs_to :src_user, class_name: 'User', optional: false
belongs_to :des_user, class_name: 'User', optional: false
end
end
class User
has_many :likes_as_src,
class_name: 'Users::Like',
foreign_key: :src_user
has_many :likes_as_des,
class_name: 'Users::Like',
foreign_key: :src_user
end
# app/models/users/like.rb
# table name is microposts_likes
module Microposts
class Like
belongs_to :user, optional: false
belongs_to :micropost, optional: false, foreign_key: 'post_id'
end
end
class User
has_many :micropost_likes, class_name: 'Microposts::Like'
has_many :liked_microposts, through: :micropost_likes, source: :micropost
end
That gives you two simple join tables with non-nullable foreign keys and good indices. It also makes the validations very straight forward.
Correct associations would be
#user.rb
has_many :microposts, dependent: :destroy
has_many :likes, through: :microposts, foreign_key: "post_id"
#micropost.rb
belongs_to :user
has_many :likes, dependent: :destroy, foreign_key: "post_id"
#like.rb
belongs_to :micropost, optional: true
After this, it works fine
I'm getting this error:
undefined method `create_postconversation' for #<Post...>
...originating from this line:
posts_controller.rb
#postconversation = #post.create_postconversation
What am I doing wrong?
post.rb
has_one :postconvo, foreign_key: "post_id", dependent: :destroy
has_one :postconversation, through: :postconvo,
class_name: "Conversation",
source: :conversation
postconvo.rb
belongs_to :post
belongs_to :conversation
validates :post_id, presence: true
validates :conversation_id, presence: true
conversation.rb
has_one :postconvo, foreign_key: "conversation_id", dependent: :destroy
has_one :post, through: :postconvo, source: :post
I have a self join model captain on my user model. I'm having an issue using the captain in an has_one relationship with a team model, which already has a has_many relationship with the user model.
My Team Model
class Team < ActiveRecord::Base
validates :teamname, :teamcolor, presence: true
has_one :captain, :class_name => "User"
#, :through => :user
has_many :users
#after_save :set_default_captain
accepts_nested_attributes_for :users
accepts_nested_attributes_for :captain
end
My User Model
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save {self.email = email.downcase }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format:{with: VALID_EMAIL_REGEX},
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
has_one :profile, inverse_of: :user, dependent: :destroy
has_many :teammates, :class_name => "User", :foreign_key => "captain_id"
belongs_to :captain, :class => "User"
belongs_to :team
accepts_nested_attributes_for :team
end
I'm having the issue using #team.captain, as captain_id is in the User database table but it's retrieving the first user with the team_id equaling #team.id. Usinghas_one :captain, :through => :user` gives association error. Any advice is appreciated. Thanks.
Try to specify foreign_key for the association.
has_one :captain, :class_name => "User", :foreign_key => 'captain_id'
I have two model User and Investment and one polymorhic model Address
class User < ActiveRecord::Base
has_one :address, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :address
end
class Investment < ActiveRecord::Base
has_many :addresses, as: :addressable, dependent: :destroy
accepts_nested_attributes_for :addresses, reject_if: lambda { |v| v['address'].blank? } && :address_blank, :allow_destroy => true
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
validates :address, presence: true
end
now validates :address, presence: true will applicable to both Investment as well as User
but i want it to applicable to only Investment not to User. so how do i do that.
Thanks.
in class Investment add
validates :address_id, presence: true
and remove bellow from class Address
validates :address, presence: true
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
validates :address, presence: true, if: :investment?
protected
def investment?
addressable_type == 'Investment'
end
end
I have users and issues joined by a votership model. Users can vote on issues. They can either vote up or down (which is recorded in the votership model). First, I want to be able to prevent users from casting multiple votes in one direction. Second, I want to allow users to cast the opposite vote. So, if they voted up, they should still be able to vote down which will replace the up vote. Users should never be able to vote on an issue twice. Here are my files:
class Issue < ActiveRecord::Base
has_many :associations, :dependent => :destroy
has_many :users, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :users, :through => :voterships
belongs_to :app
STATUS = ['Open', 'Closed']
validates :subject, :presence => true,
:length => { :maximum => 50 }
validates :description, :presence => true,
:length => { :maximum => 200 }
validates :type, :presence => true
validates :status, :presence => true
def cast_vote_up!(user_id, direction)
voterships.create!(:issue_id => self.id, :user_id => user_id,
:direction => direction)
end
end
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
end
class VotershipsController < ApplicationController
def create
session[:return_to] = request.referrer
#issue = Issue.find(params[:votership][:issue_id])
#issue.cast_vote_up!(current_user.id, "up")
redirect_to session[:return_to]
end
end
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
has_many :associations, :dependent => :destroy
has_many :issues, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :issues, :through => :voterships
end
You would put the uniqueness constraint on the Votership model. You don't need to put validations on the association itself.
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
validates :issue_id, :uniqueness => {:scope=>:user_id}
end
This means a user can only have a single vote on a given issue (up or down).
Relationship models:
class Person
has_many :accounts
has_many :computers, through: :accounts
end
class Account
belongs_to :person
belongs_to :computer
scope :administrators, -> { where(role: 'administrator') }
end
class Computer
has_many :accounts
has_many :people, through: :accounts
end
This is how it is called
person.accounts.administrators.map(&:computer)
We can do this better using ActiveRecord::SpawnMethods#merge!
person.computers.merge(Account.administrators)
Ref: https://coderwall.com/p/9xk6ra/rails-filter-using-join-model-on-has_many-through