Attributes of referenced user are all nil - ruby-on-rails

I have a post that can accept comments and those comments belong to the user and the post. I can successfully create a comment and it will be associated with the post and the user (checked that by going into console, the user_id and post_id is there), but when I try to call a user's attribute (such as user.username) it does not work. If I call a post's attribute (such as post.body) it works. When making the comments model I have referenced the user and post in my migration. In my user.rb I have also tried using has_many :comments, through: :posts but that did not work.
user.rb
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
end
comment.rb
class Comment < ApplicationRecord
belongs_to :post
belongs_to :user
end
post.rb
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
end
comments_controller.rb
class CommentsController < ApplicationController
before_action :findpost
def create
#comment = #post.comments.build(comment_params)
#comment.user_id = current_user.id
if #comment.save
redirect_to post_path(#post)
else
flash[:alert] = "Check the comment form"
end
end
def comment_params
params.require(:comment).permit(:body)
end
private
def findpost
#post = Post.find(params[:post_id])
end
end
The migration
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.string :body
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
t.timestamps
end
end
end
The specific error I get
undefined method `username' for nil:NilClass
Where comment.user.username is being called
<%= render partial: 'comments/form' %>
<% if #post.comments.count > 0 %>
<%= #post.comments.each do |comment| %>
<div>
<%= link_to author_path(id: comment.user.username) do %>
<%= comment.user.username %>
<p><%= comment.body %></p>
</div>
<% end %>
<% end %>
This error is at comment.user.username. The main problem is that whatever attribute that I call for comment.user it always returns nil (even something like id).

Related

several has_many through instances, of diffrent categories, in the same form

I've been working on a simple scenario : users can join one group of each type. I am tring to build a form that will show all types, and under each type's name- the chosen group for that type, or a select box to choose a group of that type, if the user is not a member of one.
So far, I only could come up with a seperate form for each type - rather unconvinient. I'v Been tring to solve this for several days. I found explanations for uniqness of instances, collection_select and has_many through forms but I can't find a way to a combine solution.
Models:
class User < ActiveRecord::Base
has_many :memberships
has_many : groups, through: :memberships
end
class Group < ActiveRecord::Base
has_many : memberships
has_many :users, through: :memberships
belongs_to :group_type
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :user_id, uniqueness: { scope: [:group_id, :group_type] }
end
class GroupType < ActiveRecord::Base
has_many :groups
end
View:
<% #types = GroupTypes.all %>
<% #types.each do |type| %>
<%= '#{#type.name}' %>
<% #active_group = user.groups.where(type :type) %>
<% if #active_group.exist? %>
<%= '#{#active_group}' %>
<%= link_to 'Leave', [group.user], method: :delete, data: { confirm: 'Are you sure?' } %>
<% else %>
<%= form_for (Membership.new) do |f| %>
<%= f.hidden_field :user_id, value: #user.id %>
<%= f.collection_select :group_id, Groups.all.where(type :type), :id, :name
<%= f.submit %>
<%end>
<%end>
<%end>
controlller:
Class MembershipController < ApplicationController
def create
#user = User.find(params[:user_id])
#group = Group.find(params[:group_id])
#membership = user.membership.create(group :#group )
#user. memberships << membership
redirect_to user_register_path
end
def destroy
#user = User.find(params[:user_id])
#user.groups.find_by(group : params[:group_id]).delete
redirect_to user_register_path
end
private
def membership_params
params.require(:membership).permit(:user_id, :group_id)
end
end
Not sure if it is working properly, but as I wrote I am not happy with the idea of a form for each cathegory. was wondering if anyone could advise on a solution for that basic problem.
Thanks!
not a complete answer but I thought of posting
the whole idea is by DRYING up your code you can easily see solution to your problems
1) DROP the TypeGroup model
class Group < ActiveRecord::Base
has_many : memberships
has_many :users, through: :memberships
has_many :types, class_name: "Group",
foreign_key: "type_id"
belongs_to :type, class_name: "Group"
end
migration
class CreateTypes < ActiveRecord::Migration[5.0]
def change
create_table :groups do |t|
t.references :type, index: true
t.timestamps
end
end
end
2) your controller#new
def new
#active_groups = current_user.groups.map{ |group| group.types}
#types = Type.all
end
3) use form helpers
def user_group?
type.group.user == current_user
end
4) DRY your form
<% #types.each do |type| %>
<%= '#{#type.name}' %>
<% if user_group? %>
// show your form
<%end>
// etc etc
<%end>
also I never use this architecture, of showing the child form and using it to query for the parent, but usually I always start from the parent and build a nested form

Implement "add to favorites"

I am creating an app where a user can favorite a room. I started with a has_and_belongs_to_many association. But then I just noticed that it is very tricky to implement a remove button with drestroy. So I decided to do it this time with a has_many through association. I have a users who should add rooms to favorite or wishlist. Unfortunately when I click on the favorite button I am getting this error:
What I am missing how can I make this work?
If you need further information just let me know. I have used this as a direction.
Implement "Add to favorites" in Rails 3 & 4
favorite_room.rb
class FavoriteRoom < ApplicationRecord
belongs_to :room
belongs_to :user
end
room.rb
belongs_to :user
has_many :favorite_rooms
has_many :favorited_by, through: :favorite_rooms, source: :user
user.rb
has_many :rooms
has_many :favorite_rooms
has_many :favorites, through: :favorite_rooms, source: :room
routes.rb
resources :rooms do
put :favorite, on: :member
end
rooms_controller.rb
before_action :set_room, only: [:show, :favorite]
...
...
def favorite
type = params[:type]
if type == "favorite"
current_user.favorites << #room
redirect_to wishlist_path, notice: 'You favorited #{#room.listing_name}'
elsif type == "unfavorite"
current_user.favorites.delete(#room)
redirect_to wishlist_path, notice: 'Unfavorited #{#room.listing_name}'
else
# Type missing, nothing happens
redirect_to wishlist_path, notice: 'Nothing happened.'
end
end
private
def set_room
#room = Room.find(params[:id])
end
show.html.erb
<% if current_user %>
<%= link_to "favorite", favorite_room_path(#room, type: "favorite"), method: :put %>
<%= link_to "unfavorite", favorite_room_path(#room, type: "unfavorite"), method: :put %>
<% end %>
create_favorite_rooms.rb (migration file)
class CreateFavoriteRooms < ActiveRecord::Migration[5.0]
def change
create_table :favorite_rooms do |t|
t.integer :room_id
t.integer :user_id
t.timestamps
end
end
end

Comments on multiple models

Within my rails app, I currently have comments setup to work with my posts model, which is functioning properly. How do I add comments to my books model?
Here is what I have so far:
Here is what I have in my schema for the comments:
create_table "comments", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "post_id"
t.integer "book_id"
end
In my user model:
class User < ActiveRecord::Base
has_many :comments
acts_as_voter
end
In my post model:
class Post < ActiveRecord::Base
has_many :comments
end
In my book model:
class Book < ActiveRecord::Base
has_many :comments
end
In my comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :book
belongs_to :user
acts_as_votable
end
In my comments controller:
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.user = current_user
end
respond_to do |format|
format.html {redirect_to post_path(post)}
end
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.liked_by current_user
respond_to do |format|
format.html {redirect_to #post}
end
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post = Post.find(params[:post_id])
end
end
In my routes file:
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
end
end
end
In my view:
<% #post.comments.each do |comment| %>
<%= comment.body %>
<% if user_signed_in? && (current_user != comment.user) && !(current_user.voted_for? comment) %>
<%= link_to “up vote”, like_post_comment_path(#post, comment), method: :put %>
<%= comment.votes.size %>
<% else %>
<%= comment.votes.size %></a>
<% end %>
<% end %>
<br />
<%= form_for([#post, #post.comments.build]) do |f| %>
<p><%= f.text_area :body, :cols => "80", :rows => "10" %></p>
<p><%= f.submit “comment” %></p>
<% end %>
What do I add to my comments controller to get comments working on both posts and books? What do I add to my routes file?
Thanks in advance for any help.
You don't want to specify each type of object that can hold Comment objects. That creates a headache of if-elsif-else blocks all over the place. Instead, you want things to be Commentable, and they all will have .comments on them.
This is called a polymorphic association in Active Record. So you would have your models something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Book < ActiveRecord::Base
has_many :comments, as: :commentable
end
And modify your database accordingly, it's all in the linked article. Now when you build a Comment object for a form, it will have pre-populated a commentable_id and commentable_type, which you can toss in hidden fields. Now it doesn't matter what the Comment is associated with, you always treat it the same.
I'd leave User as a separate association, since it's not really the same idea.

rails undefined method `reviewer' for #<Review

I'm creating review to my posts, all works but i dont know how to show who wrote the review
i'm trying this:
class User < ActiveRecord::Base
has_many :posts
has_many :reviewers, :class_name => 'Post', :foreign_key => 'reviewer_id'
end
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :reviews
belongs_to :reviewers, class_name: 'User', :foreign_key => 'reviewer_id'
default_scope -> { order('created_at DESC') }
end
class Review < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class ReviewsController < ApplicationController
def new
#post = Post.find(params[:post_id])
#review = #post.reviews.new(post_id:params[:post_id])
end
def create
#post = Post.find(params[:post_id])
#review = #post.reviews.build(review_params)
if #review.save
flash[:success] = "Ваш отзыв добавлен"
redirect_to post_path #post
else
render 'new'
end
end
private
def review_params
params.require(:review).permit(:post_id, :body, :reviewer_id).merge(:reviewer_id => current_user.id)
end
end
and my view
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.reviewer.email %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>
my migration
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.text :body
t.references :post, index: true
t.references :reviewer, index: true
t.timestamps
end
end
end
but rails given error undefined method `reviewer' for #
Help please dsfsdf
I think that you have a pluralization issue:
A post have many reviews by many reviewers (one for each review). But you are storing the foreign key within the post so you written the problematic line:
belongs_to :reviewers, class_name: 'User', :foreign_key => 'reviewer_id'
The issue here is that it is a singular association with a plural name.
I think that you are trying to say here that a
class Post
have_many :reviewers, class_name: 'User', through: :reviews
end
But as you are trying to access the reviewers from the review what you really need is to add:
class Review
belongs_to :reviewer, class_name: 'User'
end
Then you can access the reviewers from the review as expected:
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.reviewer.email %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>
There is also an error in User:
has_many :reviewers, :class_name => 'Post', :foreign_key => 'reviewer_id'
As it should be:
has_many :reviews, :foreign_key => 'reviewer_id'
You need to be using .user. Check the belongs_to in your model.
Review.first.user
As a previous poster pointed out, your user association for Review is :user, so your view should probably look like this:
<% #post.reviews.each do |review| %>
<p>
<strong>reviewer:</strong>
<%= review.user.name unless review.user.nil? %>
</p>
<p>
<strong>review:</strong>
<%= review.body %>
</p>
<% end %>

How to access an instance variable from a different controller whose page on which I am rendering a partial?

I have a User controller, and a Notifications controller. User has_many Notifications through a number of other models. Is there a good way to define #user in my notifications#destroy action so that I can reference it in my javascript?
On my User show page, I have something like this.
users/show.html.erb
<div>
<div id="user_notificationss_count">
"You have <%= #user.notifications.count %> notifications"
</div>
<%= render #user.notifications %>
</div>
notifications/_notification.html.erb
<div id="notification_<%= #notification.id %>">
<div>Congrats, you have earned XXX badge!</div>
<div><%= link_to 'X', notification, method: :delete, remote: true %></div>
</div>
users_controller.rb
def show
#user = User.find(params[:id])
end
notifications_controller.rb
def destroy
#notification= Notification.find(params[:id])
#notification.destroy
respond_to |format|
format.html { redirect_to :back }
format.js
end
end
notifications/destroy.js.erb
$("#notification_<%= #notification.id %>").remove();
$("#user_notifications_count").html("You have <%= #user.notifications.count %> notifications");
In the javascript, the first line with .remove(); works fine. However, the second line doesn't work because I haven't defined #user in my controller destroy action. My user model has_many notifications through multiple other models. Therefore, each notification does NOT have a specific user_id. Is there a way to get the user_id params from the user#show page that I am rendered on?
Sorry if I am not being clear. Please let me know and I will supplement with additional explanation/code. Thanks!
EDIT: ADDING MODEL CODE
user.rb
attr_accessible :name
has_many :articles
has_many :comments
has_many :badges
def notifications(reload=false)
#notifications = nil if reload
#notifications ||= Notification.where("article_id IN (?) OR comment_id IN (?) OR badge_id IN (?)", article_ids, comment_ids, badge_ids)
end
article.rb
attr_accessible :content, :user_id
belongs_to :user
has_many :notifications
comment.rb
attr_accessible :content, :user_id
belongs_to :user
has_many :notifications
badge.rb
attr_accessible :name, :user_id
belongs_to :user
has_many :notifications
notification.rb
attr_accessible :article_id, :comment_id, badge_id
belongs_to :article
belongs_to :comment
belongs_to :badge
Setting up a virtual attribute in your Notification model would work:
# app/models/notification.rb
class Notification < ActiveRecord::Base
belongs_to :article, :comment, :badge
def user
if article_id.nil? && comment_id.nil?
badge.user
elsif comment_id.nil? && badge_id.nil?
article.user
elsif badge_id.nil? && article_id.nil?
comment.user
end
end
end
Then, you could look up the parent in the destroy action of the notifications controller:
# app/controllers/notifications_controller.rb
def destroy
#notification= Notification.find(params[:id])
#user = #notification.user
...
end
You'll subsequently be able to access the #user instance variable just as you've indicated in your destroy.js.erb snippet above.

Resources