I want to create a simple form to make new reviews for a recipe that has been posted in a cookbook. I render the review form on the recipe show page, but it keeps giving me the same error:
undefined method `model_name' for nil:NilClass
When I do not render the partial new review form at app/views/recipes/show.html.erb, but instead I create the file app/views/reviews/new.html.erb, only then the form works. I do not understand why the form is not working when I try to render it at show recipe page.
Here is my code:
Simple form for:
<%= simple_form_for(#review, url: recipe_reviews_path(#recipe)) do |f| %>
<%= f.error_notification %>
<%= f.input :content %>
<%= f.input :rating %>
<%= f.button :submit, class: "btn btn-success" %>
<% end %>
Reviews controller:
class ReviewsController < ApplicationController
def new
#recipe = recipe.find(params[:recipe_id])
#review = Review.new(review_params)
end
def create
#recipe = recipe.find(params[:recipe_id])
#review = Review.new(review_params)
#review.recipe = #recipe
if #review.save
redirect_to recipe_path(#recipe)
else
render 'recipe/show'
end
end
private
def review_params
params.require(:review).permit(:content, :rating)
end
end
Recipes controller:
class RecipesController < ApplicationController
skip_before_action :authenticate_user!
def index
#recipes = Recipe.all
end
def show
#recipe = Recipe.find(params[:id])
#user = User.find(#recipe.user_id)
#full_name = #recipe.user.first_name + " " + #recipe.user.last_name
end
end
Recipe show page:
<div class="review">
<%= render 'review/new' %>
<% #recipe.reviews.each do |review| %>
<%= review.content %>
<%= review.rating %>
<% end %>
</div>
Routes:
resources :recipes, only: [:index, :show] do
resources :reviews, only: [:create]
end
Models:
class Recipe < ActiveRecord::Base
belongs_to :user
has_many :ingredients, dependent: :destroy
has_many :reviews, dependent: :destroy
validates :name, :summary, :course, :kitchen, :photo, :description, presence: true
validates :summary, length: { maximum: 30 }
mount_uploader :photo, PhotoUploader
accepts_nested_attributes_for :ingredients, reject_if: :all_blank, allow_destroy: true
end
model review:
class Review < ActiveRecord::Base
belongs_to :recipe
validates :content, length: { minimum: 20 }
validates :rating, presence: true
validates_numericality_of :rating, :greater_than_or_equal_to => 0, :less_than_or_equal_to => 5
validates :content, presence: true
end
Can anyone see the problem? Thank you in advance!
Just create new instance of Review Modal in show action of RecipesController -:
#review = Review.new
that's all, i will work. :)
Do you have new method in your RecipesController? since you using #recipe in the simpleform view. You need
def new
#recipe = recipe.find(params[:recipe_id])
#review = Review.new(review_params)
end
Related
I cannot figure out why #comments is returning nil when I am attempting to loop through it. If I use #event.comments.each do instead it works just fine. My current structure is User / Events / Comments.
Comments Controller:
class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
def create
#event = Event.find(params[:event_id])
#comment = #event.comments.create(comment_params)
#comment.user = current_user
if #comment.save
flash[:notice] = "Comment Added"
redirect_to #event
else
flash[:alert] = "Comment Not Added"
redirect_to #event
end
end
def show
#event = Event.find(params[:id])
#comments = #event.comments
end
def destroy
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
Events Controller Show Action:
class EventsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create,:edit, :update, :show,
:index, :destroy]
def show
#event = Event.find(params[:id])
end
private
def event_params
params.require(:event).permit(:start_date, :start_time, :location, :title, :description, :size, :difficulty,
:activity, :duration)
end
end
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :event
belongs_to :user
validates :body, presence: true
scope :newest, -> { order("created_at DESC") }
end
User Model:
class User < ActiveRecord::Base
has_many :created_events, class_name: 'Event', :foreign_key => "creator_id",
dependent: :destroy
has_many :registers, :foreign_key => "attendee_id", dependent: :destroy
has_many :attended_events, through: :registers, dependent: :destroy
has_many :comments, through: :events
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :lockable
validates :name, presence: true, uniqueness: true, length: { maximum: 50 }
validates :email, presence: true, uniqueness: { case_sensitive: true }
validate :validate_name
def validate_name
if User.where(email: name).exists?
errors.add(:name, :invalid)
end
end
end
Event Model:
class Event < ActiveRecord::Base
scope :latest, -> { order(date: :asc, time: :asc)}
belongs_to :creator, class_name: 'User'
has_many :registers, :foreign_key => 'attended_event_id', dependent: :destroy
has_many :attendees, through: :registers, dependent: :destroy
has_many :comments, dependent: :destroy
validates :title, presence: true, length: { maximum: 50 }
validates :description, presence: true, length: { maximum: 500 }
validates :location, presence: true
validates :start_time, presence: true
validates :start_date, presence: true
validates :activity, presence: true
validates :difficulty, presence: true
end
and lastly, the comments/_show.html.erb partial:
<% if #comments %>
<span class="results-number color-aqua-show">Comments</span>
<% #comments.each do |comment| %>
<p class="comments">
<i class="color-green fa fa-user ride-i"></i>
<%= comment.user.name %>: <%= time_ago_in_words(comment.created_at).capitalize %> ago
</p>
<p>
<i class="color-aqua fa fa-comment ride-i"></i>
<%= comment.body %>
</p>
<div class="bottom-breaker"></div>
<% end %>
<% else %>
<span class="results-number color-aqua-show">Be the first to comment!</span>
<% end %>
Show form from events:
<div class="container s-results margin-bottom-50">
<div class="row">
<div class="col-md-9">
<%= render partial: 'comments/show' %>
<%= render partial: 'comments/form' %>
</div>
</div>
</div>
Again, if I change #comments in the partial to #events.comments it will recognize that there are comments for the particular event and loop through them. This has been driving me insane for the better part of 5 hours now. Thanks.
As Pardeep Saini said, you need to add #comments to events#show:
def show
#event = Event.find params[:id]
#comments = #event.comments
end
The problem is that #comments is a variable, which needs to be defined. If it isn't defined, then you're going to receive the equivalent of an undefined error.
Thus, to fix it, you need to make sure that you're calling a defined variable; either #comments (if you've defined it), or #event.comments.
I think there is a much deeper issue with your structure (from looking at your code).
You'd be better setting it up like this:
#config/routes.rb
resources :events do
resources :comments, only: [:create, :destroy] #-> url.com/events/:event_id/comments...
end
#app/controllers/comments_controller.rb
class EventsController < ApplicationController
def show
#event = Event.find params[:id]
#comments = #event.comments
end
end
This will allow you to use the following:
#app/views/events/show.html.erb
<%= #event.title %>
<%= render #comments %>
<%= render "new_comment" %>
#app/views/events/_comment.html.erb
<%= comment.user.name %>: <%= time_ago_in_words(comment.created_at).capitalize %> ago
<%= comment.body %>
#app/views/events/_new_comment.html.erb
<%= form_for #event.comments.build do |f| %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
This will make it so that if you browse to url.com/events/1, it will output all the event's comments.
The added benefit of this setup is the ability to create / destroy comments:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :set_event
def create
#comment = #event.comments.new comment_params
#comment.user = current_user
#comment.save
end
def destroy
#comment = #event.comments.find params[:id]
#comment.destroy
end
private
def comment_params
params.require(:comment).permit(:body, :user)
end
def set_event
#event = Event.find params[:event_id]
end
end
Solved the problem. It was a very dumb error where I had show listed twice in my events controller. The bottom one was over riding the top.
I have items, that each have multiple (threaded) comments.
The threading is done via a parent key, that points towards another comment.
I just can't get the create action to work properly, I got it far enough to submit into the database, but it didn't have item_id and parent_id set.
I tried form_for [#item, #comment] instead of form_for :comment, url: item_comments_path( #item ) but it didn't help either.
When I look at comment_params in the create action I get this:
Parameters: {"utf8"=>"✓", "comment"=>{"body"=>"examplecommentgoeshere"}, "parent_id"=>"4", "commit"=>"Create Comment", "item_id"=>"4"}
Can anyone help?
Models:
class Item < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
has_many :images, as: :item,
dependent: :destroy
validates :title, presence: true,
allow_blank: false
validates :description, presence: true,
allow_blank: false
validates :user, presence: true,
allow_blank: false
validates :layout, presence: true
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :item
has_many :responses, class_name: "Comment",
foreign_key: "parent_id",
dependent: :destroy
has_one :image, as: :item,
dependent: :destroy
belongs_to :parent, class_name: "Comment"
validates :body, presence: true,
allow_blank: false,
length: { minimum: 10 }
end
Comment Controller:
class CommentsController < ApplicationController
before_filter :findParent
before_filter :find, only: [:update, :destroy, :show]
before_filter :is_logged_in?, only: [:create, :update, :destroy]
before_filter :has_permission?, only: [:update, :destroy]
def new
#comment = Comment.new
end
def create
logger.debug comment_params.inspect
#comment = current_user.comments.build(comment_params)
if #comment.save
redirect_to #item
else
logger.debug #comment.errors.inspect
session[:errorbj] = #comment
redirect_to #item
end
end
[...]
private
def comment_params
params.require(:comment).permit(:body, :parent)
end
private
def find
#comment = Comment.find(params[:id])
end
private
def findParent
#item = Item.find params[:item_id]
end
Item View:
<p>
<h3>Add a comment:</h3>
<%= render partial: 'comments/form', locals: { parent: #item } %>
</p>
Partial comments/form:
<%= form_for :comment, url: item_comments_path( #item ) do |f| %>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.label :image %><br>
<nope>
</p>
<%= hidden_field_tag :parent_id, parent.id %>
<p>
<%= f.submit %>
</p>
<% end %>
From another stackoverflow thread I concluded that the only way to create an object in a multi-association is to do something along the lines of this:
#comment = current_user.comments.build( comment_params.join( { parent_id: <parent.id> } ) )
Other stackoverflow posts however said not to do
#item = Item.new( item_params.join( { user_id: current_user.id } )
So is there really no better way?
i think there are a lot of errors in your code. i hope that i can put that into a form that might help you figure out the fix by yourself:
use form_for [#item, #comment]
findParent should be find_parent
findParent should set a #parent
findParent should find a Comment
the new action should initialize with a parent #comment = Comment.new parent: #parent
hidden_field_tag :parent_id, parent.id should be form.hidden_field :parent
the parent association should be set via comment_params
you don't need the Rails.logger statement, its all in logs/development.log
don't put objects into the session
why is there item and image?
learn more about debugging! http://nofail.de/2013/10/debugging-rails-applications-in-development/
It turns out this is much easier than it seemed.
This is how I ended up doing it:
class CommentsController < ApplicationController
before_filter :findParent, only: [:create]
[...]
def create
#comment = current_user.comments.build(comment_params)
#comment.item = #item
if #comment.save
redirect_to #item
else
session[:errorbj] = #comment
redirect_to #item
end
end
I have a model named Entry, which has many Categories. The page where I create/edit the Entry has checkboxes for every Category.
When I am editing an Entry, everything works okay. When I create the Entry, I get as an error for the #entry:
:entry_categories=>["is invalid"]
My thinking is that rails can't create the entry_categories because it doesn't know the id of the Entry ( which it shouldn't, it hasn't been assigned an id yet ).
I feel like this is a very common thing to try to do. I haven't been able to find an answer though. Here comes the code spamming, there must be something I am missing and hopefully some more experienced eyes can see it.
entry.rb
class Entry < ActiveRecord::Base
validates_presence_of :contents
validates_presence_of :title
has_many :entry_categories, dependent: :destroy
has_many :categories, through: :entry_categories
belongs_to :book
validates_presence_of :book
end
entry_category.rb
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :entry
validates_presence_of :category
end
category.rb
class Category < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
has_many :entry_categories, dependent: :destroy
has_many :entries, through: :entry_categories
end
entries_controller.rb
class EntriesController < ApplicationController
before_action :find_entry, only: [ :show, :edit, :update, :destroy ]
before_action :find_book, only: [ :new, :create, :index ]
before_action :authenticate_admin!, only: [:new, :create, :edit, :update, :destroy ]
def new
#entry = Entry.new
end
def create
#entry = #book.entries.new( entry_params )
if #entry.save
redirect_to entry_path( #entry ), notice: 'Entry Created'
else
render :new
end
end
def show
#categories = Category.joins( :entry_categories ).where( "entry_categories.entry_id = #{#entry.id} " ).select( "name, categories.id " )
#category_class = #categories.first.name.downcase.gsub( / /, '_' ) if #categories.any?
end
def index
#entries = #book ? #book.entries : Entry.all
end
def edit
end
def update
if #entry.update( entry_params )
redirect_to entry_path( #entry ), notice: 'Entry Updated'
else
render :edit
end
end
def destroy
#book = #entry.book
#entry.destroy
redirect_to book_path( #book ) , notice: 'Entry Destroyed'
end
protected
def entry_params
params.require(:entry).permit( :title, :contents, :year, :month, :day, category_ids: [] )
end
def find_entry
#entry = Entry.find( params[:id] )
end
def find_book
#book = Book.find( params[ :book_id ] )
rescue
#book = nil
end
end
_form.html.erb
<%= form_for [ #book, #entry ] do | form | %>
<%= content_tag :p, title %>
<%= form.text_field :title, placeholder: 'Title', required: true %>
<%= form.number_field :year, placeholder: 'Year' %>
<%= form.number_field :month, placeholder: 'Month' %>
<%= form.number_field :day, placeholder: 'Day' %>
<%= form.text_area :contents %>
<fieldset>
<legend>Categories</legend>
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
</fieldset>
<%= form.submit %>
<% end %>
So again, the entry_categories are invalid in the create method, in the update they are fine. It's the same html file.
There must be some way to tell rails to save the Entry before trying to save the EntryCategory?
Thanks.
I managed to get this working by taking the validations out of EntryCategory:
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :category
end
I'm not particularity happy about this solution, and would still appreciate other thoughts.
I think you can use any of the following approach:
You can use autosave functionality of Active Record association. By this, when you will save EntryCategory, it will automatically save Entry as well.
class EntryCategory < ActiveRecord::Base
belongs_to :entry , autosave: true
#rest of the code
end
You can also use before_save callback of active record. by this, whenever you will save EntryCategory, it will first call a specified method, than proceed with saving.
class EntryCategory < ActiveRecord::Base
before_save :save_associated_entries
#rest of the code
def save_associated_entries
# code to save associated entries here
end
end
Try this:
replace this code:
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
with:
<% Category.all.order(name: :asc).each do |category| %>
<div>
<%= check_box_tag "entry[category_ids][]", category.id %>
<%= category.name %>
</div>
you can format it with fieldset instead of div
I posted an earlier question about this and was advised to read lots of relevant info. I have read it and tried implementing about 30 different solutions. None of which have worked for me.
Here's what I've got.
I have a Miniatures model.
I have a Manufacturers model.
Miniatures have many manufacturers THROUGH a Productions model.
The associations seem to be set up correctly as I can show them in my views and create them via the console. Where I have a problem is in letting the Miniatures NEW and EDIT views create and update to the Productions table.
In the console the command #miniature.productions.create(manufacturer_id: 1) works, which leads me to believe I should be able to do the same in a form.
I THINK my problem is always in the Miniatures Controller and specifically the CREATE function. I have tried out a ton of other peoples solutions there and none have done the trick. It is also possible that my field_for stuff in my form is wrong but that seems less fiddly.
I've been stuck on this for days and while there are other things I could work on, if this association isn't possible then I'd need to rethink my entire application.
The form now creates a line in the Productions table but doesn't include the all important manufacturer_id.
Any help VERY much appreciated.
My New Miniature form
<% provide(:title, 'Add miniature') %>
<h1>Add a miniature</h1>
<div class="row">
<div class="span6 offset3">
<%= form_for(#miniature) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :production do |production_fields| %>
<%= production_fields.label :manufacturer_id, "Manufacturer" %>
<%= production_fields.select :manufacturer_id, options_from_collection_for_select(Manufacturer.all, :id, :name) %>
<% end %>
<%= f.label :release_date %>
<%= f.date_select :release_date, :start_year => Date.current.year, :end_year => 1970, :include_blank => true %>
<%= f.submit "Add miniature", class: "btn btn-large btn-primary" %>
<% end %>
</div>
</div>
Miniatures controller
class MiniaturesController < ApplicationController
before_action :signed_in_user, only: [:new, :create, :edit, :update]
before_action :admin_user, only: :destroy
def productions
#production = #miniature.productions
end
def show
#miniature = Miniature.find(params[:id])
end
def new
#miniature = Miniature.new
end
def edit
#miniature = Miniature.find(params[:id])
end
def update
#miniature = Miniature.find(params[:id])
if #miniature.update_attributes(miniature_params)
flash[:success] = "Miniature updated"
redirect_to #miniature
else
render 'edit'
end
end
def index
#miniatures = Miniature.paginate(page: params[:page])
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
#production = #miniature.productions.create
redirect_to #miniature
else
render 'new'
end
end
def destroy
Miniature.find(params[:id]).destroy
flash[:success] = "Miniature destroyed."
redirect_to miniatures_url
end
private
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, :production, :production_attributes)
end
def admin_user
redirect_to(root_url) unless current_user.admin?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
Miniature model
class Miniature < ActiveRecord::Base
has_many :productions, dependent: :destroy
has_many :manufacturers, :through => :productions
accepts_nested_attributes_for :productions
validates :name, presence: true, length: { maximum: 50 }
validates :material, presence: true
validates :scale, presence: true
validates_date :release_date, :allow_blank => true
def name=(s)
super s.titleize
end
end
Production model
class Production < ActiveRecord::Base
belongs_to :miniature
belongs_to :manufacturer
end
Manufacturer model
class Manufacturer < ActiveRecord::Base
has_many :productions
has_many :miniatures, :through => :productions
validates :name, presence: true, length: { maximum: 50 }
accepts_nested_attributes_for :productions
end
Instead of calling:
#production = #miniature.productions.create
Try Rails' "build" method:
def new
#miniature = Miniature.new(miniature_params)
#miniature.productions.build
end
def create
#miniature = Miniature.new(miniature_params)
if #miniature.save
redirect_to #miniature
else
render 'new'
end
end
Using the build method uses ActiveRecord's Autosave Association functionality.
See http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
You also need to update your params method, e.g.
def miniature_params
params.require(:miniature).permit(:name, :release_date, :material, :scale, productions_attributes: [:manufacturer_id])
end
Also your fields_for should be plural (I think)...
<%= f.fields_for :productions do |production_fields| %>
My link_to in my view is going to a completely different "show.html.erb" than I'd like it to. I'm basically trying to understand why the "link_to #exhibit is linking to an "Investigation" profile. I think it may have to do with my routes file or the fact that its a "belong to" relationship...but can't seem to get it workin...what should that link_to be?
UPDATE: (AS PER BROIS QUESTION)
The missing misbehaving link_to is in the <%= link_to #exhibit do %> in show.html.erb
MY EXHIBIT.RB (MODEL)
class Exhibit < ActiveRecord::Base
attr_accessible :content, :investigation_id, :name, :user_id, :media, :media_html
belongs_to :investigation
has_many :categorizations
has_many :categories, :through => :categorizations
validates :name, presence: true, length: { maximum: 140 }
validates :content, presence: true
default_scope -> { order('created_at DESC') }
auto_html_for :media do
html_escape
image
youtube(:width => 400, :height => 250)
link :target => "_blank", :rel => "nofollow"
simple_format
end
MY EXHIBIT CONTROLLER:
class ExhibitsController < ApplicationController
include AutoHtml
def new
#exhibit = Exhibit.new
end
def show
#exhibit = Exhibit.find(params[:id])
end
def index
#exhibits = Exhibit.paginate(page: params[:page])
end
def create
#investigation = Investigation.find(params[:investigation_id])
#exhibit = #investigation.exhibits.create(params[:exhibit])
if #exhibit.save
flash[:success] = "You've successfully added etc etc..."
redirect_to investigation_path(#investigation)
else
render 'new'
end
end
end
MY ROUTES.RB
resources :sessions, only: [:new, :create, :destroy]
resources :investigations do
resources :players
end
resources :investigations do
resources :exhibits
end
LASTLY MY SHOW.HTML.ERB (INVESTIGATION PROFILE)
<% #investigation.exhibits.each do |exhibit| %>
<div class="row-fluid services_circles">
<%= link_to #exhibit do %>
<div class="media">
<div class="pull-left">
<%= exhibit.media_html %>
</div>
<div class="media-body">
<h4 class="media-heading"><%= exhibit.name %></h4>
<p><%= exhibit.content %></p>
</div>
</div>
<% end %>
<% end %>
ADDED THE INVESTIGATIONS CONTROLLER
class InvestigationsController < ApplicationController
def new
#investigation = Investigation.new
end
def show
#investigation = Investigation.find(params[:id])
end
def index
#investigations = Investigation.paginate(page: params[:page])
end
def create
#investigation = Investigation.new(params[:investigation])
if #investigation.save
flash[:success] = "You've successfully created..."
redirect_to #investigation
else
render 'new'
end
end
end
ADDED THE INVESTIGATION MODEL
class Investigation < ActiveRecord::Base
# belongs_to :user
has_many :players, dependent: :destroy
has_many :exhibits, dependent: :destroy
default_scope -> { order('created_at DESC') }
end
I appreciate the help...if i need to post any more info just let me know
IN YOUR : app/contorollers/exhibits_controller.rb
def show
#investigation = Investigation.find(params[:investigation_id])
#exhibit = Exhibit.find(params[:id])
end
IN YOUR : app/views/exhibits/show.html.erb
<%= link_to investigation_exhibit_path(#investigation, #exhibit) do %>
Maybe, I think.