i'mtrying a simple feature where a user can comment on inquest post , but comment .user.username is not working ,it's rendering comment.user but does not support user attributes
create_table "comments", force: :cascade do |t|
t.string "content"
t.integer "inquest_id"
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["inquest_id"], name: "index_comments_on_inquest_id"
t.index ["user_id"], name: "index_comments_on_user_id"
end
comment_model
class Comment < ApplicationRecord
belongs_to :inquest
belongs_to :user
end
user_model is simple with has many comments association
comments create method of controller
def create
#comment = Comment.new(comment_params)
pp comment_params
#inquest = Inquest.find(params[:inquest_id])
#comment = Comment.new(comment_params)
#comment.inquest = #inquest
#comment.user = current_user
respond_to do |format|
if #comment.save
format.js do
#inquest = Inquest.find(params[:inquest_id])
end
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
I'm rendering comments in inquest's show.html.erb
Showing /Users/zunairaihsan/Desktop/fyp_ed_bolt/app/views/inquests/show.html.erb
where line #123 raised:
undefined method `user_name' for nil:NilClass
I've tried most of the ways possible , but it's not working.please let me know where I'm wrong
I assume, in inquests/show.html.erb you're displaying multiple comments, something like
<%= #inquest.comments.each do |comment| %>
<%= comment.user.user_name %>
<%= comment.content %>
<% end %>
Many comments will render without issue. Comment model and database doesn't allow user_id to be nil.
But looks like one comment's user_id doesn't have a corresponding id in users table. When you try to figure out what's going on and remove user_name
<%= #inquest.comments.each do |comment| %>
<%= comment.user %>
<%= comment.content %>
<% end %>
Sneaky broken comment probably doesn't show you anything, comment.user is nil, and because you have no validation on comment.content it could also be nil.
First, get rid of comments without user to verify this is the issue:
# this is fast enough for a few thousand comments
>> Comment.find_each { |comment| comment.destroy unless comment.user }
After this inquests/show.html.erb should be working.
To make sure this doesn't happen again:
class User
# this will delete all `user.comments` when you destroy `user`
has_many :comments, dependent: :destroy
# ...
end
To really make sure this doesn't happen again:
class CreateComment < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.references :user, null: false, foreign_key: true
# ...
end
end
end
With foreign_key constraint, your database will not let you destroy a user if they have comments. This works in tandem with dependent: :destroy. If you delete a user and rails automatically destroys all user.comments, then database will not complain.
Probably do the same for inquest as well if it's not optional.
Also comments without content are not really comments:
class Comment < ApplicationRecord
belongs_to :inquest
belongs_to :user
validates :content, presence: true
end
Related
I am unable to get my association to save in localhost:3000/controller_name/new. I believe it is due to belongs_to failing validation, but I am not sure how to fix it (beyond just dropping the association via requires:false/optional: true, the consequences of which I am not sure of). I created my association following this tutorial but it was for a previous version of rails.
I have a polymorphic address table that can belong to events, businesses, users, etc. I am trying to add it to event.
Address migration - you can see it references addressable:
class CreateAddresses < ActiveRecord::Migration[5.1]
def change
create_table :addresses do |t|
t.string :address
t.decimal :latitude, null: false, precision: 10, scale: 6, index: true
t.decimal :longitude, null: false, precision: 10, scale: 6, index: true
t.references :addressable, polymorphic: true, index: true
end
end
end
Address model:
class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true
end
Event model:
class Event < ApplicationRecord
has_one :address, as: :addressable
accepts_nested_attributes_for :address
end
Event Controller:
class EventsController < ApplicationController
#...stuff...
# GET /events/new
def new
#event = Event.new
#event.address = #event.build_address
##event.address = Address.new(addressable: #event)
##event.address = #event.create_address
##event.address = #addressable.User.new
end
#...stuff...
You can see I tried multiple methods to create the event's address, they mostly create the below item, the ones using addressable cause a Nil crash.
#<Address id: nil, address: nil, latitude: nil, longitude: nil, addressable_type: "Event", addressable_id: nil>
Event Form (Uses Simple_form gem):
<%= simple_form_for #event do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<%= f.simple_fields_for :address do |address| %>
<%= render :partial => 'shared/address/form', :locals => {:f => address} %>
<% end %>
<%= f.button :submit %>
<% end %>
Address form partial:
<!-- Google Maps Must be loaded -->
<% content_for :head do %>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCMh8-5D3mJSXspmJrhSTtt0ToGiA-JLBc&libraries=places"></script>
<% end %>
<div id="map"></div>
<%= f.input :address %>
<%= f.input :latitude %>
<%= f.input :longitude %>
Forms render fine. When I try to save I get
Started POST "/events" for 127.0.0.1 at 2017-07-01 16:06:23 -0400
Processing by EventsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"R0zlSs3UUNV3x8sQn5ocmE4jP12uOsFza7FezBAuhP4sw2MhF1OhixF8sAfDsLpfMEX7x5rhJ9HZfbKna8ncEA==", "event"=>{"name"=>"asd", "description"
=>"asd", "address_attributes"=>{"address"=>"asd", "latitude"=>"1", "longitude"=>"1"}}, "commit"=>"Create Event"}
(0.1ms) begin transaction
(0.1ms) rollback transaction
And I am kept on the new page. If I insert a byebug into create and print out #event.errors it shows:
#<ActiveModel::Errors:0x007fb29c34a9a8 #base=#<Event id: nil, name: "asd", description: "asd", min_users: nil, max_users: nil, start_time: nil, recurring: nil, created_at: nil, upd
ated_at: nil>, #messages={:"address.addressable"=>["must exist"]}, #details={:"address.addressable"=>[{:error=>:blank}]}>
How can I create the address.addressable? What are the consequences of turning off the requires validation as some SO answers suggest?
run rake db:schema:dump, and then check file inside db/schema.rb, make sure you have 2 fields as follow
* t.integer :addressable_id,
* t.string :addressable_type
and for more detail here is link about Activerecord Polymorphic Associations, if you have problem with your schema, then you can run migration as follow
t.references :addressable, polymorphic: true, index: true
as event has many address through polymorphic association, you can use this link to create the address
and below is sample code
#address = #event.addresses.build(attributes = {}, ...)
you may use #address not #event.address
Discovered the issues and wrote a blog post discussing this in depth. Essentially I was encountering 2 separate problems
1.The error I was receiving - "address.addressable"=>["must exist"]. address.addressable is the 'parent' table's tuple ID. In my case it is the Event ID. This error is trying to tell me that this foreign key is missing from the new address when we try to save it in the controller's create function. Like I said in the question, you can set optional:true to ignore this problem, and it somehow magically gets filled in after saving the Event. Or you can manually assign it in the create function before it saves.
def create
#event = Event.new(event_params)
#event.address.addressable = #event #<<<<<<<<<<< manually assign address.addressable
respond_to do |format|
if #event.save #Saves the event. Addressable has something to reference now, but validation does not know this in time
format.html { redirect_to #event, notice: 'Event was successfully created.' }
format.json { render :show, status: :created, location: #event }
else
#dont forget to remove the 'byebug' that was here
format.html { render :new }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
2.Event ID was a string. In my address migration I was using t.references :addressable, polymorphic: true, index: true which is an alias for and addressable_id of integer type. I needed to change my migration to use string IDs - so delete the references line and add this in instead.
t.string :addressable_id, index: true
t.string :addressable_type, index: true
This is covered in slightly more detail in the blog post.
I have like and dislike buttons that are working, but the error message for voting more than once is not.
The object type that User can vote on is called Course. I'm listing all courses on courses index page along with their like/dislike buttons. I am trying to make it so if you try to like or dislike the same course object twice, the page renders a flash message informing you that you can only vote once.
Here is the controller for likes.
#likeables_controller.rb
class LikeablesController < ApplicationController
before_action :logged_in_user
def create
course = Course.find(params[:liked_id])
count = course.likers.count
current_user.like(course)
new_count = course.likers.count
if new_count > count
redirect_to courses_url
else
# This isn't working
flash[:danger] = "You can only vote on a course once!"
redirect_to courses_url
end
end
def destroy
like = Likeable.find(params[:id])
like.destroy
redirect_to courses_url
end
end
My courses index is rendering courses partial like so:
# index.html.erb
<%= render #courses %>
The course partial in turn renders the like and dislike buttons
# _course.html.erb
<ul class="stats">
<li>
<%= render partial: 'dislike', locals: {course: course} %>
</li>
<li>
<%= render partial: 'like', locals: {course: course} %>
</li>
</ul>
# _like.html.erb
<%= form_for(current_user.likeables.build, remote: true) do |f| %>
<div><%= hidden_field_tag :liked_id, course.id %></div>
<%= button_tag type: 'submit', class: "btn" do %>
<i class="fa fa-thumbs-o-up" aria-hidden="true"></i>
<span id="likers"><%= course.likers.count %></span>
<% end %>
<% end %>
And incase it's needed, here are the relevant parts of my models and schema:
create_table "dislikeables", force: :cascade do |t|
t.integer "disliker_id"
t.integer "disliked_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["disliked_id"], name: "index_dislikeables_on_disliked_id"
t.index ["disliker_id", "disliked_id"],
name: "index_dislikeables_on_disliker_id_and_disliked_id", unique: true
t.index ["disliker_id"], name: "index_dislikeables_on_disliker_id"
end
create_table "likeables", force: :cascade do |t|
t.integer "liker_id"
t.integer "liked_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["liked_id"], name: "index_likeables_on_liked_id"
t.index ["liker_id", "liked_id"],
name: "index_likeables_on_liker_id_and_liked_id", unique: true
t.index ["liker_id"], name: "index_likeables_on_liker_id"
end
My models:
# user.rb
class User < ApplicationRecord
# User has many courses
has_many :courses, dependent: :destroy
has_many :likeables, foreign_key: "liker_id",
dependent: :destroy
has_many :likes, through: :likeables, source: :liked
has_many :dislikeables, foreign_key: "disliker_id",
dependent: :destroy
has_many :dislikes, through: :dislikeables, source: :disliked
def like(course)
likes << course
end
def dislike(course)
dislikes << course
end
end
# course.rb
class Course < ApplicationRecord
has_one :user
has_many :likeables, foreign_key: "liked_id",
dependent: :destroy
has_many :likers, through: :likeables, source: :liker
has_many :dislikeables, foreign_key: "disliked_id",
dependent: :destroy
has_many :dislikers, through: :dislikeables, source: :disliker
def liker?(user)
likes.include?(user)
end
def disliker?(user)
dislikes.include?(user)
end
end
# likeable.rb
class Likeable < ApplicationRecord
belongs_to :liked, class_name: "Course"
belongs_to :liker, class_name: "User"
validates :liked_id, presence: true
validates :liker_id, presence: true
end
# dislikeable.rb
class Dislikeable < ApplicationRecord
belongs_to :disliked, class_name: "Course"
belongs_to :disliker, class_name: "User"
validates :disliked_id, presence: true
validates :disliker_id, presence: true
end
there's a problem in your action,
def create
like = current_user.likes.where(liked_id: params[:liked_id]).first_or_initialize
if like.new_record? && like.save
redirect_to courses_url
else
redirect_to courses_url, flash: { danger: "You can only vote on a course once!" }
end
end
Maybe in controller try:
redirect_to courses_url, flash: { danger: "You can only vote on a course once!" }
And make sure in the application layout, you have the renderer for flash messages.
Try this one
redirect_to courses_url, alert: "You can only vote on a course once!"
I'm getting a "undefined method `name' for nil:NilClass" when I'm trying to get the users name of a comment from the articles controller. I've tryed to do the same request from the comments controller and it works just fine.
NoMethodError in Articles#show
Showing C:/xampp/htdocs/app/views/comments/_show.html.erb where line #2 raised:
undefined method `name' for nil:NilClass
I' using ruby -v 2.1.5 and rails -v 4.2.2
Here is my comments controller code:
class CommentsController < ApplicationController
before_filter :logged_in_user, only: [:create, :destroy]
def show
#comments= Comment.all
end
def new
#comment = Comment.new
end
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
#comment.user_id = current_user.id
if #comment.save
flash[:success] = "Comment created!"
redirect_to article_path(#article)
end
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
And my Articles controller code
class ArticlesController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: [:edit, :update, :destroy]
def show
#article = Article.find(params[:id])
#user = #article.user
#comments = #article.comments
end
...
private
def article_params
params.require(:article).permit(:title, :lead, :content, :hashtags, :front_picture, :category_id, :league_id, :front_pic_src)
end
end
And the views:
/articles => show
<!-- A little peace of my Articles view where I use "user" but under the #article -->
<h3> Article Autor</h3>
<div id="about-info">
<div class="post-profile-img-wrapper">
<%= link_to gravatar_for(#article.user, size: 50), #article.user %>
</div>
<span class="user"><%= link_to #article.user.name, #article.user %> </span>
</div>
<!-- And here I call the render for comments-->
<section class="comments">
<h2>Comments</h2>
<%= render 'comments/show' %>
</section>
And the rendered partial is _show.html.erb inside the comments view
<% #comments.each do|c| %>
<h1><%= c.user.name %></h1>
<% end %>
What am I doing wrong? I've tryed to create an instance variable in articles for user comments and it doesn't work.
The comments table has a foreing key but it's pointing to the article_id I don't know if this is the best way, but it shouldn't affect the controllers behaviour, right?
UPDATE
Here is my comments schema, it may help
create_table "comments", force: :cascade do |t|
t.text "body", limit: 65535
t.integer "article_id", limit: 4
t.integer "user_id", limit: 4
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "comments", ["article_id"], name: "index_comments_on_article_id", using: :btree
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
UPDATE FOR Models
Comment:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :article
default_scope -> { order(created_at: :desc) }
validates :body, presence: true
end
Article:
class Article < ActiveRecord::Base
belongs_to :category
belongs_to :user
belongs_to :league
has_many :comments, :dependent => :destroy
default_scope -> { order(created_at: :desc) }
mount_uploader :front_picture, ArticleFrontUploader
validates :user_id, presence: true
validates :category_id, presence: true
validates :content, presence: true
#validate :picture_size
private
# Validates the size of an uploaded picture.
def picture_size
if front_picture.size > 5.megabytes
errors.add(:front_picture, "should be less than 5MB")
end
end
end
Related peace of user
class User < ActiveRecord::Base
has_many :microposts, dependent: :destroy
has_many :articles, dependent: :destroy
has_many :comments, dependent: :destroy
...
Thanks!
STEP ONE: Setting up a partial
You can try naming local variables when rendering a partial. For example, in /articles/_show.html.erb, you would enter the following at the (current) rendering line:
<%= render partial: 'comments/show', locals: {comments: #comments} %>
That will treat the /comments/_show.html.erb as a piece of a page, rather than a complete one. This is ideal for your situation, since the page itself is about articles, but you only want to render a part of the page to show comments.
The locals hash sets up variables so your partial can render the correct objects, assuming they are named in the current controller. (In this case, the current controller is ArticlesController.) After setting local values, your next step in the comments show page is to replace #comments with simply comments.
Now, say you have no comments associated with the article. It is the same as Comment.where(article_id: #article.id), which is an array. Calling each on an empty array will take the first element, pretty much nil, and raise an error. In the app I work on, the best way to handle this is to prepend the code block with the following:
unless comments.empty?
This makes sense, because why show contents for comments if there are none present? Mind you, if you plan to have a form for entering comments, place that form outside the unless statement, and below the comments show partial.
STEP TWO: Editing associations
I do not know how you want to set this up exactly, so rather than provide copy/paste code, I will just explain. In most cases, comments do not stand alone, they always belong to something. A comment immediately belongs to an article. In your models, your associations should have a user having a comment through an article. Given the code you provided, I believe the hierarcy is the following:
User
Article
Comment
You need a user to make an article. You need an article to make a comment. While a user is required to make a comment, it is not a direct association. Again, I will not provide you with the exact code, as you may want to handle this slightly differently, but along the same lines.
The mistake is in the _show.html.erb in the comments view. The comment has only a user_id attribute. So if you call #comment.user there is nothing to show since the comment doesn't have user attribute. You have to search for the user by the user_id
<% #comments.each do |c| %>
<h1><%= User.find_by_id(c.user_id).name %></h1>
<% end %>
Try this. It would solve the problem.
Pass the locals to partial
<%= render partial: 'comments/show', locals: { comments: #comments } %>
comments/_show.html.erb
<% comments.each do|c| %>
<h1><%= c.try(:user).try(:name) %></h1>
<% end %>
I'm trying to set up a page where there are 4 dropdown boxes, each of which have a full list of Products. A user can select any combination of 4 products, and 'create' a new print page, which has the product information list
I only have one box right now, but when I try to create a new row for Print from this page, it doesn't return anything to :p1
new.html.erb:
<%= f.collection_select :p1, Product.all, :id, :name, :prompt => 'Select One' %>
<%= f.submit "Create" %>
class PrintsController < ApplicationController
def new
#print = Print.new
end
def create
#print = Print.new(print_params)
if #print.save
redirect_to #print, alert: "Created successfully."
else
redirect_to new_print_path, alert: "Error creating print page."
end
end
def show
#print = Print.find(params[:id])
end
private
def print_params
params.require(:p1).permit(:p2, :p3, :p4)
end
end
Model
class Print < ActiveRecord::Base
belongs_to :product
end
Migrate
class CreatePrints < ActiveRecord::Migration
def change
create_table :prints do |t|
t.integer :p1
t.integer :p2
t.integer :p3
t.integer :p4
t.timestamps
end
end
end
Routes:
Rails.application.routes.draw do
resources :categories, :products, :prints
I'm a total rails newbie, so I know I'm probably making a stupid mistake somewhere, but I've been fiddling with code for hours and still haven't figured out what I did wrong.
Your print_params method is wrong :
def print_params
params.require(:print).permit(:p1, :p2, :p3, :p4)
end
This is the right format.
Ive been trying to build an messaging system for my site which uses devise for authentication. The functionality it requires is to be able to send a message to either one or more recipients (preferably with a checklist form listing users as well). After searching for a while I found a couple gems such as mailboxer, but I didn't need all its features and wanted to build my own system for sake of learning (still a newbie at rails).
I have followed this ancient tutorial ( http://web.archive.org/web/20100823114059/http://www.novawave.net/public/rails_messaging_tutorial.html ). I realize this is a very old tutorial but it is the only one I could find which matched what I was trying to achieve.
I have followed the tutorial to a T and even copied and pasted the code from the tutorial after my code didn't work.
when trying to access http://localhost:3000/mailbox i get a NoMethodError in MailboxController#index
undefined method `messages' for nil:NilClass
app/controllers/mailbox_controller.rb:12:in `show'
app/controllers/mailbox_controller.rb:6:in `index'
I have also referenced this question Rails 3: undefined method messages for Folder which had the same error as me but the topic just seemed to go no where.
mailbox_controller.rb
class MailboxController < ApplicationController
def index
#folder = current_user.inbox
show
render :action => "show"
end
def show
#folder ||= current_user.folders.find_by(params[:id])
#messages = #folder.messages :include => :message, :order => "messages.created_at DESC"
end
end
models/folder.rb
class Folder < ActiveRecord::Base
acts_as_tree
belongs_to :user
has_many :messages, :class_name => "MessageCopy"
end
Any help with this would be awesome, also just let me know if you need any more info and will post it.
I ended up figuring out the messaging system with a few modifications. I wanted to post my whole solution since it gave me a difficult time and might be useful to others. I kept it very simple and did not include the the folder model which was giving me the problem in the first place, but none the less it is functioning.
Heres are my associations
model/message.rb
attr_reader :user_tokens
belongs_to :sender, :class_name => 'User'
has_many :recipients
has_many :users, :through => :recipients
def user_tokens=(ids)
self.user_ids = ids
end
model/recipient.rb
belongs_to :message
belongs_to :user
model/user.rb
has_many :messages, :foreign_key => :sender_id
This is my messages controller
messages_controller.rb
def new
#message = Message.new
#user = current_user.following
#users = User.all
# #friends = User.pluck(:name, :id).sort
end
def create
#message = current_user.messages.build(message_params)
if #message.save
flash[:success] = "Message Sent!"
redirect_to messages_path
else
flash[:notice] = "Oops!"
render 'new'
end
end
def index
#user = User.find(current_user)
#messages = Recipient.where(:user_id => #user).order("created_at DESC")
end
private
def message_params
params.require(:message).permit(:body, :sender_id, user_tokens: [])
end
My Views
_form.html.erb
</div>
<!-- displays the current users frinds their following -->
<%= f.select :user_tokens, #user.collect {|x| [x.name, x.id]}, {}, :multiple => true, class: "form-control" %>
<br>
<div class="modal-footer">
<%= f.button :submit, class: "btn btn-primary" %>
</div>
Schema
messages_table
t.text "body"
t.integer "sender_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "body_html"
recipients_table
t.integer "message_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
I hope this helps.