Acts_as_commentable_with_threading - comments are not adding - ruby-on-rails

#post.comments.all is clear. and i dont see any errors after i send form. When i click "Submit" i sent to posts/id/comments, but
my routes.rb
resources :posts do
resources :comments
end
post controller
def show
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#commenter = #current_user
#post = Post.find(params[:id])
#comment = Comment.build_from( #post, #commenter.id, "234234" )
#comments = Comment.all
respond_to do |format|
format.html
format.json { render json: #post }
end
end
comments controller
class CommentsController < ApplicationController
def index
#comments = #post.comments.all
end
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
end
post model
acts_as_commentable
has_many :comments
comment model
class Comment < ActiveRecord::Base
acts_as_nested_set :scope => [:commentable_id, :commentable_type]
attr_accessible :commentable, :body, :user_id
validates :body, :presence => true
validates :user, :presence => true
# NOTE: install the acts_as_votable plugin if you
# want user to vote on the quality of comments.
#acts_as_votable
belongs_to :commentable, :polymorphic => true
# NOTE: Comments belong to a user
belongs_to :user
# Helper class method that allows you to build a comment
# by passing a commentable object, a user_id, and comment text
# example in readme
def self.build_from(obj, user_id, comment)
new \
:commentable => obj,
:body => comment,
:user_id => user_id
end
#helper method to check if a comment has children
def has_children?
self.children.any?
end
# Helper class method to lookup all comments assigned
# to all commentable types for a given user.
scope :find_comments_by_user, lambda { |user|
where(:user_id => user.id).order('created_at DESC')
}
# Helper class method to look up all comments for
# commentable class name and commentable id.
scope :find_comments_for_commentable, lambda { |commentable_str, commentable_id|
where(:commentable_type => commentable_str.to_s, :commentable_id => commentable_id).order('created_at DESC')
}
# Helper class method to look up a commentable object
# given the commentable class name and id
def self.find_commentable(commentable_str, commentable_id)
commentable_str.constantize.find(commentable_id)
end
end
post view
%h2 Add a comment:
- #comments.each do |c|
= #c.body
= form_for([#post, #comment]) do |f|
.field
= f.label :body
%br/
= f.text_area :body
.actions
= f.submit
Thanks in advance and sorry for bad english

First of all you can debug why #comment.save return false yourself - just add p #comment.errors in else block and check server log.
It seems for me that you try to save invalid comments because you don't have setup user for #comment in action CommentsController#create. Comment validates presence of user!
There are several ways how to fix it. Analyzing your code I think the simplest way for you is modify CommentsController#create
#CommentsController
def create
#current_user ||= User.find(session[:user_id]) if session[:user_id]
#post = Post.find(params[:post_id])
#comment = #post.comments.new params[:comment]
#comment.user = #current_user
if #comment.save
redirect_to #post # comment not save, so i dont redirect to this page
else
# is that there
end
end
Another way is to use some gem for authentication - I recommend devise
One more way (very bad way) is to pass user_id through hidden field (you have defined #current_user in PostsController#show and user_id in attr_accessible list in Comment). But this is easy way to hack your application and write comments on behalf of any user in system!

Related

Rails Setting Devise Username as attribute of comment

I'm new to rails and still figuring out which things belong in the model and which in the controller. I'm creating a simple comment model that belongs to articles. I have a attribute :commenter which is a string. I would like to get the username from the current_user (I'm using devise for my login feature) Would I do this in the create method of my controller?
Something like
def create
#post = Post.find(params[:post_id])
#comment.commenter = current_user.username
#comment = #post.comments.create(comment_params)
redirect_to post_path(#post)
end
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :user #should have user_id: integer in Comment
belongs_to :post #should have post_id: integer in comment
delegate :username, to: :user, allow_nil: true
end
In posts controller: -
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.new(comment_params)
#comment.user = current_user
if #comment.save
flash[:success] = "Comment saved successfully!"
redirect_to post_path(#post)
else
flash[:error] = #comment.errors.full_messages.to_sentence
redirect_to post_path(#post)
end
end
After that you can get all user details of any comment:-
comment = Comment.find(#id_of_comment)
comment.username => #will return username because of delegation
Reference for delegation

Error getting user's name and email from the associated model in Ruby on Rails

I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url

Rails: Creating new model based on a parameter in another model's controller

I'm trying to create a model where every blog post has a number of tags (a separate model) associated to it. In my form, I pass back a separated string of tags along with the post, like "apples,oranges,bananas". I'm eventually going to try and split this string and then create a tag for each string. However, for now I'm just trying to see if I can make one tag with the whole string.
The request is handled by the create method of the posts controller. However, I can't figure out how to create and commit the tag in the posts controller, or at least call the tags controller to delegate the creation. I get the error:
undefined method `new=' for Tag(id: integer, tag_name: string, post_id: integer):Class
This points to the line Tag.new = #post.tags.create(post_params[:tag_name]) of the posts_controller.
Relevant code:
posts_controller
def new
#post = Post.new
end
def create
#post = current_user.posts.create(post_params)
Tag.new = #post.tags.create(post_params[:tag_name])
if #post.save
redirect_to post_path(#post), :notice => "Post was created successfully."
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :content_md, :image, :user_id, :year_created, :medium, :dimension_height, :dimension_width, :measurement, :weight_in_pounds, :price, :quantity)
end
tags_controller
class TagsController < ApplicationController
def new
#tag = Tag.new
end
def create
#tag = tag.create(tag_params)
end
private
def tag_params
params.require().permit(:tag_name, :user_id)
end
end
post.rb
class Post < ActiveRecord::Base
# Relations
belongs_to :user
has_many :comments
has_many :post_votes
has_many :tags
accepts_nested_attributes_for :tags
end
tag.rb
class Tag < ActiveRecord::Base
validates :tag_name, presence: true
validates :post_id, presence: true
belongs_to :post
end
Edit:
Also tried:
def create
#tag = Tag.new(post_params[:tag_name])
#tag.save
#post = current_user.posts.create(post_params)
# #post.tags.create(post_params[:tag_name])
if #post.save
redirect_to post_path(#post), :notice => "Post was created successfully."
else
render :new
end
end
for the posts controller. It creates the post, but doesn't seem to actually save the #tag
Without going into much deep, I see you you've the following line in your code (in posts_controller)
Tag.new = #post.tags.create(post_params[:tag_name])
which is not correct. If you want to just create (but not persist) you should call,
#post.tags.new(post_params[:tag_name])
For persist
#post.tags.create(post_params[:tag_name])
This will get rid of the error that you've posted. However, you shouldn't require these as your Post model can accept nested attributes for tags. Just make sure you've generated form correctly (hint: fields_for)
This line
Tag.new = #post.tags.create(post_params[:tag_name])
has no sense. Without further discussion:
#post.tags.create(post_params[:tag_name])
will work and actually create the tag.

Ruby on Rails Saving in two tables from one form

I have two models Hotel and Address.
Relationships are:
class Hotel
belongs_to :user
has_one :address
accepts_nested_attributes_for :address
and
class Address
belongs_to :hotel
And I need to save in hotels table and in addresses table from one form.
The input form is simple:
<%= form_for(#hotel) do |f| %>
<%= f.text_field :title %>
......other hotel fields......
<%= f.fields_for :address do |o| %>
<%= o.text_field :country %>
......other address fields......
<% end %>
<% end %>
Hotels controller:
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
address = #hotel.address.build
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
But this code doesn't work.
ADD 1
Hotel_params:
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price)
end
ADD 2
The main problem is I don't know how to render form properly. This ^^^ form doesn't even include adress fields (country, city etc.). But if in the line
<%= f.fields_for :address do |o| %>
I change :address to :hotel, I get address fields in the form, but of course nothing saves in :address table in this case. I don't understand the principle of saving in 2 tables from 1 form, I'm VERY sorry, I'm new to Rails...
You are using wrong method for appending your child with the parent.And also it is has_one relation,so you should use build_model not model.build.Your new and create methods should be like this
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #here
end
def create
#hotel = current_user.hotels.build(hotel_params)
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Update
Your hotel_params method should look like this
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price,address_attributes: [:country,:state,:city,:street])
end
You should not build address again
class HotelsController < ApplicationController
def new
#hotel = Hotel.new
end
def create
#hotel = current_user.hotels.build(hotel_params)
# address = #hotel.address.build
# the previous line should not be used
if #hotel.save
flash[:success] = "Hotel created!"
redirect_to #hotel
else
render 'new'
end
end
Bottom line here is you need to use the f.fields_for method correctly.
--
Controller
There are several things you need to do to get the method to work. Firstly, you need to build the associated object, then you need to be able to pass the data in the right way to your model:
#app/models/hotel.rb
Class Hotel < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
#app/controllers/hotels_controller.rb
Class HotelsController < ApplicationController
def new
#hotel = Hotel.new
#hotel.build_address #-> build_singular for singular assoc. plural.build for plural
end
def create
#hotel = Hotel.new(hotel_params)
#hotel.save
end
private
def hotel_params
params.require(:hotel).permit(:title, :stars, :room, :price, address_attributes: [:each, :address, :attribute])
end
end
This should work for you.
--
Form
Some tips for your form - if you're loading the form & not seeing the f.fields_for block showing, it basically means you've not set your ActiveRecord Model correctly (in the new action)
What I've written above (which is very similar to that written by Pavan) should get it working for you

How to limit current scope of nested form to first item

I'm creating a simple discussion board in Rails. Every new Topic creates also a first Reply that includes the content. This is my current schema.
Topic
> title:string
> user_id: integer
has_many :replies
accepts_nested_attributes_for :replies
Reply
> topic_id: integer
> user_id: integer
> content: text
belongs_to :topic
The current topics/_form.html.haml is something like this
= form_for #topic fo |f|
= f.text_field :title
= f.fields_for :replies
= reply.text_area :content
The problem is when trying to edit a Topic, I see all the list of replies as editable since it's iterating the fields_for :replies field in the form partial. I should only see the first one.
What would be a convenient way to limit this iteration to its current first available reply only while also building a new one if a topic is new?
I ended up with something like this that works but I guess there should be a better way.
# Topic model
has_one :owner_reply, class_name: 'Reply'
accepts_nested_attributes_for :owner_reply
# Form partial view
= form_for #topic fo |f|
- reply_resource = (#topic.new_record? ? :replies : :owner_reply)
= f.text_field :title
= f.fields_for :replies
= reply.text_area :content
These are the full TopicsController#create and update actions.
def create
#board = Board.find(params[:board_id])
#topic = #board.topics.new(topic_params)
#topic.user_id = current_user.id
#topic.replies.each { |reply| reply.user_id = current_user.id }
if #topic.save
respond_to do |format|
format.html { redirect_to topic_path(#topic) }
end
else
render :new
end
end
def update
#topic = Topic.find(params[:id])
if #topic.update_attributes(topic_params)
respond_to do |format|
format.html { redirect_to topic_path(#topic) }
end
else
render :edit
end
end
I would use a scoped association, the same way you are using :owner_reply but adding a scope to limit to the first record, you can also add a order to it if you need
class Topic
has_many :replies
has_many :first_replies, -> { first }, class_name: 'Reply'
accepts_nested_attributes_for :replies
accepts_nested_attributes_for :first_replies
And in your view
= form_for #topic fo |f|
...
= f.fields_for :first_replies
= reply.text_area :content
Create a class method on Topic that returns the first Reply:
class Topic
accepts_nested_attributes_for :first_reply
def self.first_reply
self.replies.first
end
# ...
end
Then call the class method in fields_for.

Resources