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.
Related
I cannot get validation errors to work. I have a create product page which has two form_for's One for products and one for photos. When a product doesnt upload you get sent to redirect_to new_product_path
product controller
def new
#product = Product.new
#photo = Photo.new
end
def create
check_for_image = Photo.where(:product_id => nil, :user_id => current_user)
if check_for_images == []
redirect_to products_new_path, :notice => "Add an image then press start before submit"
else
#product = current_user.products.create(params[:product])
if #product.save
Photo.where(:product_id => nil, :user_id => current_user).update_all(:product_id => #product.id)
render "show", notice: "Product created!"
else
redirect_to new_product_path #, :flash => { :error => "Test!" }
# render "new"
end
end
end
I tried to do render "new" instead of redirect_to but i get undefined method `model_name' for NilClass:Class with the error pointing to the photo form for
haml create product page
= form_for #photo, :html => { :multipart => true, :id => "fd" } do |f|
%span Add files...
= f.file_field :image
= form_for #product,:url => products_path, :html => { id: "fd", multipart: true } do |f|
- if #product.errors.any?
.error_messages
%h2 Form is invalid
%ul
- for message in #product.errors.full_messages
%li
= message
%p
= f.text_field :name, placeholder: "Name"
%p
= f.text_field :price, class: "auto", data: { a_sign: "$ " }, placeholder: "Price"
%p
= f.text_field :description, placeholder: "Description"
%p.button.start
= f.submit
product model
validates :user_id, presence: true
validates :name, presence: true, length: { minimum: 5 }
validates :price, presence: true, numericality: { greater_than_or_equal_to: 3.00 }
I think first you need to know difference between redirect_to & render
redirect_to :action => 'new' #It will call the method 'new' and then it will call
#respective file. In this case it is `new.haml`
render :action => 'new' #It will call `new.haml` directly without calling method 'new'
So when you used render :action => 'new' it will not get #photo and hence it is giving error undefined methodmodel_name' for NilClass:Classeither you have to handlenil` on view or
It will fixed when you
Change
redirect_to new_product_path
To
#photo = Photo.new
render :action => 'new'
render will render a particular view using the instance variables available in the action. For example if a render was used for the new action, when a user goes to /new, the new action in the controller is called, instance variables are created and then passed to the new view. Rails creates the html for that view and returns it back to the user’s browser. This is what you would consider a normal page load.
redirect_to will send a redirect to the user’s browser telling it to re-request a new URL. Then the browser will send a new request to that URL and it will go through the action for that URL, oblivious to the fact that it was redirected to. None of the variables created in the action that caused the redirect will be available to the redirected view. This is what happens when you click on ‘Create’ in a form and the object is created and you’re redirected to the edit view for that object.
so you had no validation errors because each time you did a redirect a new instance was created with no errors.
2 lines you have to change:
render "show", notice: "Product created!"
and
redirect_to new_product_path
not sure about this:
redirect_to products_new_path, :notice => "Add an image then press start before submit" it's not clear what it does and how your app should behave.
your controller:
def new
#product = Product.new
#photo = Photo.new
end
def create
check_for_image = Photo.where(:product_id => nil, :user_id => current_user)
if check_for_images == []
redirect_to products_new_path, :notice => "Add an image then press start before submit"
else
#product = current_user.products.create(params[:product])
if #product.save
Photo.where(:product_id => nil, :user_id => current_user).update_all(:product_id => #product.id)
redirect_to #product, notice: "Product created!"
else
render action: "new"
end
end
end
more:
Are redirect_to and render exchangeable?
http://blog.markusproject.org/?p=3313
I have the following:
Clients have many Reports and Reports belong to a client.
However on the creation of the Report it is not assigning the client_id into the database, but not sure why?
Am i doing something wrong here?
Client Model
class Client < ActiveRecord::Base
has_many :reports, :dependent => :destroy
end
Report Model
class Report < ActiveRecord::Base
has_attached_file :report
belongs_to :client
end
Client Controller (Update)
# PUT /clients/1
# PUT /clients/1.json
def update
#client = Client.find(params[:id])
respond_to do |format|
if #client.update_attributes(params[:client])
format.html { redirect_to [:admin,#client], :notice => 'Client was successfully updated.' }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.json { render :json => #client.errors, :status => :unprocessable_entity }
end
end
end
Report Controller (Create)
# POST /reports
# POST /reports.json
def create
#report = Report.new(params[:report])
#report.client_id = params[:client][:client_id]
respond_to do |format|
if #report.save
format.html { redirect_to '/admin/clients', :notice => 'Report was successfully created.' }
format.json { render :json => #report, :status => :created, :location => #report }
else
format.html { render :action => "new" }
format.json { render :json => #report.errors, :status => :unprocessable_entity }
end
end
end
Client Edit View
<%= form_for([:admin, #client.reports.build]) do |f| %>
<label class="formlabel">Report Upload</label>
<%= f.file_field :report, :class=>"text-input small-input" %>
<div class="actions">
<br />
<%= f.submit 'Upload', :class => 'button' %>
</div>
<% end %>
Assistance would be appreciated!
I'm curious; because you're using .build in the form_for, the client may already be in the url.
What if you remove:
#report.client_id = params[:client][:client_id]
and submit, what happens then? Because this line is looking incorrectly at the params, so I wonder if you are overwriting that you built in the form_for
Either that, or a hidden field like #Adam said would work.
The client_id doesn't have a related input field in the form on your view. You could add something to your form like:
f.hidden_field :client_id
And then in your controller, set it as:
#report.client_id = params[:report][:client_id]
Alternatively, you could include the client_id in the url.
Stupid Mistake it seems needed to up the end function on the form-for for the client to close it off before opening the form-for the reports.
Then add the field for the client_id and now just hide the field as per Adam suggestion.
Thanks Steph for suggestions as this help me solve this mistake.
Thanks Everyone! :-)
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 %>
I am trying to create form that contains another model in rails. I have accomplished this with using accepts_nested_attibutes and it is working great. The problem is I have an additional field in that table that records the User Name for each comment and I am not sure on how to insert that information when a new comment is being created. The username is being supplied by the Application Controller using the "current_user" method.
Regards,
Kyle
Comment Model
class Comment < ActiveRecord::Base
belongs_to :post
before_save :set_username
private
def set_username
self.created_by = current_user
end
end
Application Controller (This is just a Sandbox app so I just put a string in the method)
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
def current_user
"FName LName"
end
end
Show View
<p id="notice"><%= notice %></p>
<p>
<b>Title:</b>
<%= #post.title %>
</p>
<div id="show_comments"><%= render 'comments' %></div>
<div id="add_comments">
Add Comment
<%= form_for #post, :url => {:action => 'update', :id => #post.id}, :html => { :'data-type' => 'html', :id => 'create_comment_form' } do |f| %>
<%= f.fields_for :comments, #new_comment do |comment_fields| %>
<%= comment_fields.text_area :content %>
<%end%>
<div class="validation-error"></div>
<%= f.submit %>
<% end %>
</div>
Post Controller
def update
#post = Post.find(params[:id])
respond_to do |format|
if #post.update_attributes(params[:post])
#comments = #post.comments.all
format.html { redirect_to({:action => :show, :id => #post.id}, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
I was originally thinking you could just set it as a default or a before_save in the model. But models don't have access to current_user. So it's probably best to just set the current user in the controller. It's not as DRY as putting it in the model but it's less hackey and potentially problematic this way.
def update
#post = Post.find(params[:id])
#post.attributes = params[:post]
#post.comments.each do |comment|
comment.created_by = current_user if comment.new_record?
end
respond_to do |format|
if #post.save
#comments = #post.comments.all
format.html { redirect_to({:action => :show, :id => #post.id}, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post }
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
Just want to point out that it is possible to access current_user in the model scope. In this case I don think it is necessary, as the solution from #aNoble should work. So if is possible to set the current_user from the controller, I would prefer that.
In short, we add a method to the User class
class User < ActiveRecord::Base
cattr_accessor :current_user
def self.current_user
#current_user ||= User.new("dummy-user")
end
...
end
and in your application controllor, we add a before_filter that sets it. Make sure to call this filter after your authentication is done.
class ApplicationController < ActionController::Base
before_filter { |c| User.current_user = current_user }
end
And, then inside your Comment-model you could just do something like
class Comment
before_create :set_user
def set_user
created_by = User.current_user unless created_by
end
end
(so I only set the created_by if it was not yet set, and only upon creation of a new comment).
I'm saving nested objects within the objects they belong to, but when i do that they do not use the controller im saving but the parents controller.
class Project < ActiveRecord::Base
belongs_to :company
belongs_to :user
has_many :tasks
accepts_nested_attributes_for :tasks, :allow_destroy => true
end
in the views i have something like this
<% form_for #project do |c| %>
<% c.fields_for :tasks, #project.tasks.last do |p| %>
<%= p.text_field :name %>
<% end %>
<%= submit_tag '+' %>
<% end %>
so what i'm trying to do, is update the user field with the fields for, that last field is specified in the controller.
def show
#project = Project.find(params[:id])
#project.tasks.build
#project.tasks.last.user = current_user # this should pass to the show.html.erb, to be saved back
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #project }
end
end
I'm thinking maybe the solution would be to check if the username is set in the nested objects, and if not to populate it with the current user in:
def update
#project = Project.find(params[:id])
#project.user = current_user
#find anything #project.....user blank and set to current user
respond_to do |format|
if #project.update_attributes(params[:project])
format.html { redirect_to(#project, :notice => 'Project was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #project.errors, :status => :unprocessable_entity }
end
end
end
I'm hoping that is the solution, and how do it do it?
an example of it running currently is at http://severe-fire-37.heroku.com