No Method Error for "comments", can't find and correct the nil value to make the comment post - ruby-on-rails-3

I'm relatively new to rails and am trying to pull off my first polymorphic association with comments.
I am running rails 3.2.3
Edit - When I try to post a comment, my log is returning this error:
Started POST "/comments" for 127.0.0.1 at 2012-05-20 13:17:38 -0700
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"SOLcF71+WpfNLtpBFpz2qOZVaqcVCHL2AVZWwM2w0C4=", "comment"=>{"text"=>"Test this comment"}, "commit"=>"Create Comment"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 101 LIMIT 1
Completed 500 Internal Server Error in 126ms
NoMethodError (undefined method `Comment' for nil:NilClass):
app/controllers/comments_controller.rb:13:in `create'
I have tried out many different solutions offered on SO and elsewhere, including the answer from Jordan below, due, I'm sure, to my own inexperience, but have been unable to resolve the error.
The trace calls out line 13 in the Comments Controller and I commented after that line below to mark the error:
class CommentsController < ApplicationController
def index
#commentable = find_commentable
#comments = #commentable.comments
end
def new
#post = Post.find(params[:post_id])
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment]) #<<<<LINE 13
if #comment.save
flash[:notice] = "Successfully created comment."
redirect_to :id => nil
else
render :action => 'new'
end
end
private
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
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
Comment template (in post show)
<ul id="comments">
<% if #comments %>
<h2>Comments</h2>
<% #comments.each do |comment| %>
<li><%= comment.text %></li>
<% end %>
<% else %>
<h2>Comment:</h2>
<% end %>
</ul>
<%= simple_form_for [#commentable,Comment.new], :html => { :class => 'form-horizontal', :multipart => true } do |f| %>
<fieldset>
<%= f.input :text %>
Upload Photo <%= f.file_field :photo %>
</fieldset>
<div class="form-actions">
<%= f.submit nil, :class => 'btn btn-primary' %>
</div>
<% end %>
Post show:
<p id="notice"><%= notice %></p>
<div class="row">
<div class="span2 offset1">
<%= image_tag #post.photo.url(:show) %>
</div>
<div class="span5">
<h1><%= #post.title %></h1>
<p><%= #post.index_text.html_safe %></p>
<p><%= #post.show_text.html_safe %></p>
<%= render "comments/comment" %>
<%= render "comments/form" %>
<% if can? :update, #course %>
<%= link_to 'Edit Post', edit_post_path(#post), :class => 'btn btn-mini' %>
<%= link_to 'Delete Post', #post,
confirm: 'Are you sure?',
method: :delete,
:class => 'btn btn-mini' %>
<%= link_to 'New Post', new_post_path, :class => 'btn btn-mini' %>
<% end %>
</div>
<nav class="span2 offset1">
<ul class="well">
<li>Category 1</li>
<li>Category 2</li>
</ul>
</nav>
</div>
<div class="row offset2">
<%= link_to 'Back to Posts', posts_path, :class => 'btn btn-mini' %>
</div>
Routes:
resources :posts, :has_many => :comments
resources :comments
It is probably something obvious that someone with more experience can resolve. Let me know if anything comes to mind. Brian

The problem is that #commentable is nil, which means that CommentsController#find_commentable is returning nil. I think your regular expression is sound, so that means one of two things is happening in find_commentable:
There aren't any keys in params that match your regex.
Your regex is matching but there aren't any records in the resulting table with the id in value.
Debug this as usual by inspecting params and the records in your database to make sure they look like you expect them to look.

The problem is your find_commentable method.
Here are the params passed to your CommentsController#create:
Started POST "/comments" for 127.0.0.1 at 2012-05-20 13:17:38 -0700
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"SOLcF71+WpfNLtpBFpz2qOZVaqcVCHL2AVZWwM2w0C4=", "comment"=>{"text"=>"Test this comment"}, "commit"=>"Create Comment"}
Here is your CommentsController#create:
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment]) #<<<<LINE 13
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
As you can see, find_commentable expects a param like xx_id (for example, comments_id) which it uses to search for an appropriate class (in case of comments_id, it will be Comment), otherwise it returns nil. Refer classify and constantize here.
Your params do not contain any such param. So, you always get a nil object.
Your find_commentable needs some rework. I think in case of nested_fields, it should be an expression like
/(.+)_attributes$/
instead of
/(.+)_id$/.
And you need to have
:accepts_nested_attributes_for :commentable
in your Comment model class.

I tried both of the above answers, but the problem continued.
I ended up consulting with a friend who suggested the following solution, which I like because it's more elegant than my original attempt and easier to read (for later, when I or someone else need to return to the code):
def find_commentable
if params[:post_id]
Post.find(params[:post_id])
#elsif params[:other_id]
# Other.find(params[:other_id])
else
# error out?
end
end
The commented out section will refer to other associations once I get them up and running.

Related

What are some possible reasons that you would get an undefined method 'TheModelName_path' error using a form_for in rails 3?

I can't figure out why I have this error from my knowledge everything is set up as it should to work.
this is the error i get:
NoMethodError at /short/new
undefined method `shorts_path' for #<#:0x007fbc441426e0>
my model is short not shortS
when I run rake routes there is no shorts_path so I'm not sure where this helper is coming from. I don't understand why the form_for is giving me this error when #short is defined in the def new section of my controller.
Can someone please explain this to me?
Thank you in advance
This is what my controller looks like
class ShortController < ApplicationController
def show
#short = Short.find(params[:id].to_i(36))
respond_to do |format|
#redirect directly to the url stored as long in the database
format.html { redirect_to #short.long}
end
end
def new
#short = Short.new
respond_to do |format|
format.html # new.html.erb
end
end
def create
#short = Short.new(params[:short])
respond_to do |format|
if #short.save
format.html { render action: "show" }
else
format.html { render action: "new" }
end
end
end
end
routes
Trainingproject::Application.routes.draw do
root :to => 'short#welcome'
resources :short, :only => [:new, :create, :show]
end
form partial which is rendered in the view for new
<%= form_for(#short) do |f| %>
<% if #short.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#short.errors.count,"error") %> prohibited this url from being saved:</h2>
<ul>
<% #short.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-field">
<%= f.label "Enter your URL" %><br />
<%= f.text_field :long %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
You're getting this error because form_for(#short), where #short is a new Short record, will attempt to use the path helper called shorts_path to route to where the form needs to go. You're missing this route definition in your config/routes.rb file:
resources :shorts
Please read the Getting Started and Routing guides, which should explain the things that you're missing.

ActiveAdmin - Pass locals to form

In my photos.rb file I have:
ActiveAdmin.register Photo do
form :partial => 'admin/forms/photo', :locals => { :events => Event.all }
end
In my _photo.html.erb file I want to be able to access the value of events, but it doesn't seem to be detectable. How can I do this?
As requested in a comment, here is my form:
<% if events.any? %>
<%= form_for [:admin, #photo], :validate => true do |f| %>
<!-- insert a bunch of standard field divs here -->
<div class="actions" style="margin-top: 20px">
<% if #photo.new_record? %>
<%= f.submit 'Create photo' %>
<% else %>
<%= f.submit 'Update photo' %>
<% end %>
</div>
<% end %>
<% else %>
<div style="font-size: 14px">
You cannot add photos until you have added at least one event.
</div>
<% end %>
The error message I am getting is around the events.any? line:
Completed 500 Internal Server Error in 108ms
ActionView::Template::Error (undefined local variable or method `events' for #<#<Class:0x007fdb8e841b80>:0x007fdb8a93e7a8>)
form do |f|
f.render partial: 'admin/forms/photo', locals: { f: f, events: Event.all }
end
Note that you will need to remove form_for, semantic_form_for, active_admin_form_for etc from your partial, since it will be covered by the form do part in the admin/photos.rb, otherwise there will be two nested forms.
Example partial:
# app/views/admin/forms/_photo.html.arb
if events.any?
f.inputs do
f.input :title, label: 'Etc'
f.input :file
end
f.actions
else
f.div 'You cannot add photos until you have added at least one event.',
style: 'font-size: 14px'
end
form do |f|
render partial: 'admin/forms/photo', locals: { value: 'random_data' }
end
You can use f.render or just render.
Note that partial: is required to send locals to the form
Example Partial
#app/views/admin/forms/_photo.html.arb
active_admin_form_for [:admin, resource] do |f|
f.semantic_errors *f.object.errors.keys
f.inputs do
f.input :name
f.input :random if (value == 'random_data')
end
f.actions
end
another thought on this, if you want to check only if there are any Events in db you can make any call directly on Class:
<% if Event.any? %>
do this
<% else %>
do that
<% end %>
without sending variables to partial, the above code result in:
2.0.0p0 :004 > Event.any?
(0.6ms) SELECT COUNT(*) FROM "events"
=> true
and leave the ActiveAdmin partial without locals:
ActiveAdmin.register Photo do
form :partial => 'admin/forms/photo'
end

Create a select from existing or create new in a form Rails 3

So I'm trying to set a name attribute of organizations and create a new organization or choose from a previously existing organization from the same form.
I've tried to follow Ryan Bates' railscast on the topic here: http://railscasts.com/episodes/57-create-model-through-text-field
I have also tried numerous solutions from stack. However, I can't quite seem to get it to run (that and I have a validation that does not recognize the virtual attribute I'm using)
so my organizations model:
class Organization < ActiveRecord::Base
has_many :materials
has_many :users
has_and_belongs_to_many :causes
has_and_belongs_to_many :schools, :join_table => 'organizations_schools'
####The following line has been edited ####
attr_accessible :name, :unlogged_books_num, :id, :new_organization_name
attr_accessor :new_organization_name
before_validation :create_org_from_name
validates_presence_of :name
def self.assign_school_to_organization(org, school)
orgschool = OrganizationsSchool.create(:organization_id=> org.id, :school_id=> school[0])
end
def create_org_from_name
create_organization(:name=>new_organization_name) unless new_organization_name.blank?
end
end
I have also tried the create_org_from_name as the following:
def create_org_from_name
self.name = new_organization_name
end
And this does not change the name to the organization name before validating or saving the instance.
I have also tried to change the before_save to before_validation, and that has not worked
My controller for organization (I also tried to change this in create)
def create
respond_to do |format|
#organization = Organization.new(params[:organization])
#organization.name = #organization.new_organization_name unless #organization.new_organization_name.blank?
if #organization.save
#school = params[:school]
Organization.assign_school_to_organization(#organization, #school)
format.html { redirect_to #organization, notice: 'Organization was successfully created.' }
format.json { render json: #organization, status: :created, location: #organization }
else
format.html { render action: "new" }
format.json { render json: #organization.errors, status: :unprocessable_entity }
end
end
end
And finally, I have what my form is doing currently:
<%= form_for(#organization) do |f| %>
<% if #organization.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#organization.errors.count, "error") %> prohibited this organization from being saved:</h2>
<ul>
<% #organization.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% #schools = School.all %>
<% #organizations = Organization.all %>
<div class="field">
<%= f.label 'Organization Name' %><br />
<%= f.collection_select(:name, #organizations, :name, :name, :prompt=>"Existing Organization") %>
Or Create New
<%= f.text_field :new_organization_name %>
</div>
<div class="field">
<%= f.label :unlogged_books_num %><br />
<%= f.number_field :unlogged_books_num %>
</div>
<div class="field">
<%= f.label 'School' %><br />
<% school_id = nil %>
<%= collection_select(:school, school_id, #schools, :id, :name) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
==================================EDIT============================================
So currently, when I try to make an organization with something only written in the virtual text field, My log tells me the following:
Processing by OrganizationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"igoefz8Rwm/RHrHLTXQnG48ygTGLydZrzP4gEJOPbF0=", "organization"=> {"name"=>"", "new_organization_name"=>"Virtual Organization", "unlogged_books_num"=>""}, "school"=>["1"], "commit"=>"Create Organization"}
Rendered organizations/_form.html.erb (7.1ms)
Rendered organizations/new.html.erb within layouts/application (8.0ms)
Completed 200 OK in 17ms (Views: 12.2ms | ActiveRecord: 1.0ms)
================================EDIT 2============================================
So this is what I get from the rails console if I try to create a new organization running this command: Organization.create(:new_organization_name=>"Virtual Organization", :unlogged_books_num=>"3")
irb(main):001:0> Organization.create(:new_organization_name=>"Virtual Organization", :unlogged_books_num=>"3")
(0.1ms) BEGIN
(0.1ms) ROLLBACK
=> #<Organization id: nil, name: nil, unlogged_books_num: 3, created_at: nil, updated_at: nil>
If the function of create_org_from_name is self.name = new_organization_name, then the result of the same command from the console is blank:
irb(main):002:1> Organization.create(:new_organization_name=>"Virtual Organization", :unlogged_books_num=>"3")
irb(main):003:1>
You need:
before_validation :create_org_from_name
and
def create_org_from_name
self.name = new_organization_name if not new_organization_name.blank?
end
You don't want to do a create in your before_validation method.

Why am I getting "no route matches [POST]" in my nested resources?

I have a project that contains projects that have todos that have tasks. When I try to create a new task, I get this error when I submit:
No route matches [POST] "/projects/1/todos/19/tasks/new"
Here is my form:
<%= form_for [#todo, #todo.tasks.new], :url => new_project_todo_task_path(#project, #todo) do |f| %>
<div class="field">
<%= f.label :description, "Description" %><br />
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit %> or <%= link_to "Cancel", "#", :id => "cancel_new_task_link" %>
</div>
<% end %>
Here is my controller:
class TasksController < ApplicationController
before_filter :authenticated?
before_filter :get_project_and_todo
respond_to :html, :xml, :json, :js
def new
#task = #todo.tasks.new
end
def create
#task = #todo.tasks.new(params[:task])
if #task.save
respond_with #todo, :location => project_todo_path(#project, #todo)
else
render "new"
end
end
private
def get_project_and_todo
#project = Project.find(params[:project_id])
#todo = #project.todos.find(params[:todo_id])
end
end
Here are my routes:
resources :projects do
resources :todos do
resources :tasks
end
end
Thanks
Your URL should not be new_project_todo_task_path(#project, #todo). You don't need to specify the URL here as Rails will imply it from the parameters passed in to form_for.
If the final object is a new object and not persisted in the database then it will make a POST request to, in this case, /projects/:project_id/todos. You're declaring in your example that you want to make a POST request to /projects/:project_id/todos/new, for which there is no POST route and that is why it's failing.

2 render templates (from devise) on one page (rails 3)

I've installed devise for my rails app, i can go to the sign in page or the sign up page. But I want them both on a welcome page...
So I've made a welcome_page_controller.rb with the following function:
class WelcomePageController < ApplicationController
def index
render :template => '/devise/sessions/new'
render :template => '/devise/registration/new'
end
end
But when i go to the welcome page i get this error:
NameError in Welcome_page#index
Showing /Users/tboeree/Dropbox/rails_projects/rebasev4/app/views/devise/sessions/new.html.erb where line #5 raised:
undefined local variable or method `resource' for #<#<Class:0x104931c>:0x102749c>
Extracted source (around line #5):
2: <% #header_title = "Login" %>
3:
4:
5: <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
6: <p><%= f.label :email %><br />
7: <%= f.email_field :email %></p>
8:
Does anybody knows a solution for this problem? Thanks in advance!
Does it have to do with the fact that it is missing the resource function? in the welcome_page controller? It's probably somewhere in the devise controller...?
Regards,
Thijs
Here's how I managed to did it.
I've put a sign up form in my home#index
My files:
view/home/index.html.erb
<%= render :file => 'registrations/new' %>
helper/home_helper.rb
module HomeHelper
def resource_name
:user
end
def resource
#resource = session[:subscription] || User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
def devise_error_messages!
return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count,
:resource => resource_name)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
end
You need that part because Devise works with something called resource and it should be defined so you can call your registration#new anywhere.
Like that, you should be able to register. However, I needed to display errors on the same page. Here's what I added:
layout/home.html.erb (the layout used by index view)
<% flash.each do |name, msg| %>
# New code (allow for flash elements to be arrays)
<% if msg.class == Array %>
<% msg.each do |message| %>
<%= content_tag :div, message, :id => "flash_#{name}" %>
<% end %>
<% else %>
# old code
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<% end %> #don't forget the extra end
<% end %>
I found this code here
And here's something I created: I saved my resource object if invalid in a session so that the user hasn't to fill every field again. I guess a better solution exists but it works and it's enough for me ;)
controller/registration_controller.rb
def create
build_resource
if resource.save
if resource.active_for_authentication?
# We delete the session created by an incomplete subscription if it exists.
if !session[:subscription].nil?
session[:subscription] = nil
end
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
else
set_flash_message :notice, :inactive_signed_up, :reason => resource.inactive_message.to_s if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords(resource)
# Solution for displaying Devise errors on the homepage found on:
# https://stackoverflow.com/questions/4101641/rails-devise-handling-devise-error-messages
flash[:notice] = flash[:notice].to_a.concat resource.errors.full_messages
# We store the invalid object in session so the user hasn't to fill every fields again.
# The session is deleted if the subscription becomes valid.
session[:subscription] = resource
redirect_to root_path #Set the path you want here
end
end
I think I didn't forget any code. Feel free to use whatever you need.
Also, you can add your sign in form in the same page (something like that:)
<%= form_for("user", :url => user_session_path) do |f| %>
<%= f.text_field :email %>
<%= f.password_field :password %>
<%= f.submit 'Sign in' %>
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
<%= link_to "Forgot your password?", new_password_path('user') %>
<% end %>
Cheers !