How to hookup validations for a mongoid embedded resource - ruby-on-rails

I have a comments model embedded in a posts model:
class Post
include Mongoid::Document
field :name, :type => String
field :content, :type => String
embeds_many :comments
end
class Comment
include Mongoid::Document
field :content, :type => String
validates_presence_of :content
embedded_in :post, :inverse_of => :comments
end
The comment form, as expected, is included in posts/show:
p#notice = notice
h2= #post.name
p= #post.content
a.btn href=edit_post_path(#post) Edit
h3 New comment
= simple_form_for [#post, Comment.new] do |f|
= f.error_notification
= f.input :content, :as => :text
= f.button :submit
And in my comments controller:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #post, notice: 'Comment created.' }
else
format.html { render :template => "posts/show" }
end
end
end
This seems to work for valid comments, but has two problems for blank comments. First, no validation message is shown. Second, the blank comment is rendered in the post/show template because it is included in #post (even though it isn't persisted) after calling #comment = #post.comments.build(params[:comment]).
The railscast on this topic uses #post.comments.create!(params[:comment]) instead of build, but this leads to an exception on any validation failure which I don't think is appropriate.
I could get around the second problem by refetching the post, but this seems kludgy:
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #post, notice: 'Comment created.' }
else
#post = Post.find(params[:post_id])
format.html { render :template => "posts/show" }
end
end
end
And even still, the validation is missing.
Does anyone have a suggestion for how this could be done better?

Related

Rails Saving User Selected Foreign Key In Form

I am newbie to Rails and I have been struggling with this idea that just can't get over my head. For example I have
class Survey < ActiveRecord::Base
has_many :questions
end
class Question < ActiveRecord::Base
belongs_to :survey
end
I have created a set of surveys. Now I want to create some questions and assign it to the survey through its foreign key survey_id. In the Question new.html.erb page, I used advanced form to show the Survey ID(I followed this tutorial). It works fine however, when I click submit, it seems like the survey_id doesn't save.
This is my question_controller.rb
def create
#question = Question.create(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
else
format.html { render :new }
end
end
def question_params
params.require(:question).permit(:description, :date_created, :survey_id)
end
Here is the form:
<%= form_for(#question) do |f| %>
<div class="field">
<%= f.label :survey_id %><br>
<%= collection_select(:question, :survey_id, Survey.all, :id, :description, prompt: true ) %>
</div>
<% end %>
I know in order for this to work, I have to do something like
#question = #survey.questions.create(...)
but I have no idea how to get the #survey instance before the user click on the drop down and select the appropriate survey.
Anyone has any idea on how to do this ??
Your create method should be
def create
#survey = Survey.find(params[:survey_id])
#question = #survey.questions.create(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
else
format.html { render :new }
end
end
or you can also use filter
class QuestionsController < ApplicationController
before_filter :set_survey, only: :create
def create
#question = #survey.questions.create(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
else
format.html { render :new }
end
end
private
def set_survey
#survey = Survey.find(params[:survey_id]) || Survey.new
end
end
you have survey objects created and you want them to be associated to questions,
so, on your question's form if you are using select drop-down with survey names to select from then set select options values to survey id. so your question params will contain survey_id parameter with value equal to selected survey's id. and thus Question.create(question_params) will create question with survey_id.

Ruby on Rails: Form routes to a different URL

I have a form that saves data, but it gets routed to the wrong URL.
If my form is in
localhost:3000/users/1/styles/1
And when I submit the form, I get redirected to this:
localhost:3000/styles/1
and then I get an error:
Couldn't find User without an ID
views/comments/_form.html.erb
<%= form_for [#commentable, #comment] do |f| %>
<%= f.text_area :content, rows: 3 %>
<%= f.submit %>
<% end %>
styles_controller.rb
def show
#user = User.find(params[:user_id])
#style = #user.styles.find(params[:id])
#commentable = #style
#comments = #commentable.comments
#comment = Comment.new
end
comments_controller.rb
before_filter :get_commentable
def new
#comment = #commentable.comments.new
end
def create
#comment = #commentable.comments.new(params[:comment])
#comment.user = current_user
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
render :new
end
end
private
def get_commentable
#commentable = params[:commentable].classify.constantize.find(commentable_id)
end
def commentable_id
params[(params[:commentable].singularize + "_id").to_sym]
end
routes.rb
resources :styles do
resources :comments, :defaults => { :commentable => 'style' }
end
Please let me know if there's other information that is needed. Why am I getting rerouted to a different url? My comment does save into my database.
Thank you
If you want to go back to localhost:3000/users/1/styles/1 after creating comment, you should change
if #comment.save
redirect_to #commentable, notice: "Comment created."
else
to
if #comment.save
redirect_to [User.find(params[:user_id]), #commentable], notice: "Comment created."
else
Edit: Should use User that owns the style not current user

Simple Rails Blog - Post Comments are ignoring Post ID [outside comment array in parameters]

I have a simple blog I'm building with Rails, and I'm following the normal rails getting started guide (http://guides.rubyonrails.org/getting_started.html). I'm setting up a form for comments inside my post's show method, but when I save, it's not saving the page_id in the comment record.
= form_for [#post, #post.comments.build], :remote => true do |f|
.field
= f.label :name
= f.text_field :name
.field
= f.label :extra_field, #page.rsvp_extra_field
= f.text_area :extra_field
.actions
= f.submit 'Save'
post.rb
class Post < ActiveRecord::Base
has_many :comments, dependent: :destroy
attr_accessible :comments_attributes, :comment_attributes
accepts_nested_attributes_for :comments, :allow_destroy => true
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
attr_accessible :extra_field, :name, :post_id, :phone
end
I see in the rails console that it's posting it, but putting NULL for post_id. Any thoughts?
EDIT
I didn't change my create method at all:
def create
#comment = Comment.new(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to post_url, notice: 'Comment was successfully created.' }
format.json { render json: #comment, status: :created, location: #comment }
else
format.html { render action: "new" }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
EDIT 2
I think my parameters are nested when I don't want them to be... any ideas how to get this "post_id" inside of the "comment" array?
Parameters: {"utf8"=>"✓", "authenticity_token"=>"eqe6C7/ND35TDwtJ95w0fJVk4PSvznCR01T4OzuA49g=",
"comment"=>{"name"=>"test", "extra_field"=>""}, "commit"=>"Save", "post_id"=>"8"}
Because your method create in CommentsController, create object Comment unrelated to the object Post. has_many relation provide 3 methods for related objects:
post.comments.create
post.comments.create!
post.comments.build(eq new method)
Add in your CommentsController this:
...
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
end
...

Runtime error - called id for nil in Rails 3 project

I'm getting this error when I try to submit my form (/POSTS/SHOW):
RuntimeError in Posts#show
Showing /Users/fkhalid2008/loand/app/views/posts/show.html.erb where line #1 raised:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Extracted source (around line #1):
1: <%= form_remote_tag (:update => 'message', :url => {:controller => 'main', :action => 'send_message', :user_id => #post.user.id}) do %>
2: <br>
3: <br />
4: <br />
How do I fix this?
Relevant code is below:
/VIEWS/POSTS/SHOW
<%= form_remote_tag (:update => 'message', :url => {:controller => 'main', :action => 'send_message', :user_id => #post.user.id}) do %>
<br>
<br />
<br />
<div class="field">
Hello! My name is <%= f.text_field :subject %> and I'm contacting you in response to your ad. I'm interested in learning more so get in touch! Here's my contact details: <%= f.text_field :body %>.
Submit
<% end %>
POST MODEL
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :title, :job, :location, :salary
validates :title, :job, :location, :salary, :presence => true
validates :salary, :numericality => {:greater_than_or_equal_to => 1}
default_scope :order => 'posts.created_at DESC'
end
USER MODEL
class User < ActiveRecord::Base
has_many :posts
has_one :profile
has_private_messages
attr_accessible :email
validates_presence_of :email
validates_uniqueness_of :email, :message =>"Hmm, that email's already taken"
validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/i, :message => "Hi! Please use a valid email"
end
POSTS CONTROLLER
def show
#post = Post.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render :json => #post }
end
end
def new
#post = Post.new
#post.user = current_user
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #post }
end
end
def edit
#post = Post.find(params[:id])
end
def create
#post = Post.new(params[:post])
#post.user = current_user
respond_to do |format|
if verify_recaptcha && #post.save
format.html { redirect_to :action=> "index"}
format.json { render :json => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.json { render :json => #post.errors, :status => :unprocessable_entity }
end
end
end
def update
#post = Post.find(params[:id])
#post.user = current_user
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, :notice => 'Post was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #post.errors, :status => :unprocessable_entity }
end
end
end
APPLICATION CONTROLLER (this is where I am defining current_user)
class ApplicationController < ActionController::Base
protect_from_forgery
private
def current_user
#_current_user ||= session[:current_user_id] &&
User.find_by_id(session[:current_user_id])
end
end
MAIN CONTROLLER (send_message is defined here)
class MainController < ApplicationController
def send_message
message = Message.new
message.subject = params[:subject]
message.body = params[:message]
message.sender = User.find session[:user]
message.recipient = User.find params[:user_id]
if message.save
ContactMailer.deliver_message_email message.recipient.email, message.id, request.host
return redirect_to "/posts"
else
render :text => "Hmm. Something seems to be wrong...let me look into it"
end
end
You don't have a user assigned to the post record represented by the #post instance variable.
Presumably a user needs to be logged in to make a post?
Also presumably you have a current user defined somewhere?
Your controller actions that use this form need to assign the user to the post record
def new
#post = Post.new
#post.user = current_user # You will need to get the current user from somewhere
respond_to do |format|
format.html # new.html.erb
format.json { render :json => #post }
end
end
UPDATE
To make sure that your current user is assigned you should add a check to ensure the user is logged in in the controller actions. This is normally done by adding a before filter to authorize the current user which will redirect back to the login page if the current use is logged out.
Have a look at this rails cast to explain logging in and out and redirecting on a before filter http://railscasts.com/episodes/250-authentication-from-scratch
There is a revised version of the cast here but you will need a subscription for that
http://railscasts.com/episodes/250-authentication-from-scratch-revised
well worth paying for IMO
End of update
You will need to / should also assign the current user in whatever actions update the post record - i.e. the create and update actions in EXACTLY the same way.
Also, because you have not got a user assigned to a post record then you need to handle this scenario in the form so that you don't get 500 errors
You can use the #post.user.blank? boolean check to help you with this
Something like
<% if #post.user.blank? %>
<h2>There is no user assigned to this post record! This should never happen ad you should never see this message, please contact support if etc... </h2>
<% else %>
<!-- Place all your current form code here -->
<% end %>
You are getting the error because #post.user is nil in :user_id => #post.user.id.
Make sure you define #post in your post controller's show action and that it has a valid user association.

form_for tag and nested form - to use custom method from controller

I'm new to Rails and making application where college members (teachers and students) can create posts and comment on them. Later on I wish to add nesting (ancestry) and points system in it.
I have Post, Comment and Member model. The Post model was made via Scaffolding, Member model was made with help of Devise, and Comment is just a model.
In my show page of Post, I'd like to have comments beneath the posts, I've made some progress (thanks to SO I came to know quite a bit) but now I am stuck with a problem that whenever I attempt to post a blank comment, rails was redirecting to the edit page. How to change this so that rails stays only on the show page and display errors?
For this I searched a bit, created a new method 'update_comments' in post_controller.rb and tried modifying the forms_for tag attributes, as in the code below, but now I get routing error on submitting.
app/models/member.rb
class Member < ActiveRecord::Base
#Associations
belongs_to :department
has_one :student, :dependent => :destroy
accepts_nested_attributes_for :student
has_one :nstudent, :dependent => :destroy
accepts_nested_attributes_for :nstudent
has_many :posts, :dependent => :destroy
has_many :comments, :dependent => :destroy
end
app/models/post.rb
class Post < ActiveRecord::Base
#Associations
belongs_to :member
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
app/models/comment.rb
class Comment < ActiveRecord::Base
# Associations
belongs_to :member
belongs_to :post
validates_presence_of :content
end
config/routes.rb
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
member do
get 'update_comment'
end
end
root :to => 'posts#index'
app/controllers/posts_controller.rb
class PostsController < ApplicationController
# Devise filter that checks for an authenticated member
before_filter :authenticate_member!
# GET /posts
# GET /posts.json
def index
#posts = Post.find(:all, :order => 'points DESC')
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
...
# GET /posts/1/edit
def edit
#post = Post.find(params[:id])
end
# POST /posts
# POST /posts.json
def create
#post = Post.new(params[:post])
#post.member_id = current_member.id if #post.member_id.nil?
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Post was successfully created.' }
format.json { render json: #post, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# PUT /posts/1
# PUT /posts/1.json
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
# DELETE /posts/1
# DELETE /posts/1.json
def destroy
#post = Post.find(params[:id])
#post.destroy
respond_to do |format|
format.html { redirect_to posts_url }
format.json { head :no_content }
end
end
# Not made by scaffold
def update_comment
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Comment was successfully created.' }
format.json { head :no_content }
else
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
end
app/views/posts/show.html.erb
<p> Have your say </p>
<%= form_for #post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>
image of my show page:
http://i.stack.imgur.com/TBgKy.png
on making a comment:
http://i.stack.imgur.com/JlWeR.png
Update:
Looked back and made changes here, following what Ken said. I don't know how but it works for now.
app/controllers/posts_controller.rb
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { head :no_content }
elsif :comments
format.html { render action: "show" }
format.json { render json: #post.errors, status: :unprocessable_entity }
else
format.html { render action: "edit" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
You don't need a custom method. It is not very RESTful. See, e.g., http://www.sitepoint.com/restful-rails-part-i/ for info on REST. This is not a case where there is justification to use a custom method.
Whenever you find yourself adding custom methods you should think long and hard about whether it's necessary. Usually if you need custom methods what you actually need is another controller (or a different set of controllers).
The update method here is all you need. If you really want to go to the show method after a failed update (though I don't know why) then change the render edit call in the block in the update method after the update fails.
It seems like your real problem is the edit view isn't showing errors. Although the scaffold generated view should do that so maybe you changed it.
In case you missed it you may also benefit from this screencast:
http://railscasts.com/episodes/196-nested-model-form-part-1
You need to update the method type in route and also needs to sets the form post method to your new action, also when you submit a form its an post request not a get request.
Urdxxx::Application.routes.draw do
devise_for :members
resources :posts do
collection do
post :update_comment
end
end
root :to => 'posts#index'
<p> Have your say </p>
<%= form_for :post, :url => {:action => 'update_comment'} do |p| %>
<%= p.fields_for :comments do |c| %>
<!-- Following 3 lines saved my life -->
<% if c.object.new_record? %>
<%= c.text_area :content, :rows => 4 %>
<%= c.hidden_field :member_id, value: current_member.id %>
<% end %>
<% end %>
<%= p.submit "Reply" %>
<% end %>

Resources