NoMethodError in Controller#action - undefined met

2019-07-17 04:38发布

问题:

I'm building an application that has a Keynote model and a Story model (as well as a User model that I implemented with Devise). Keynotes have many Stories and a Story belongs to a Keynote.

I'm having problems creating new stories and I get the following error:

NoMethodError in StoriesController#create

undefined method `keynote' for #User:0x007fba0e32f760

The error happens on line 18 of stories_controller.rb which is

@story = @current_user.keynote.stories.build(params[:story])

in the create method.

This is part of my stories_controller.rb

class StoriesController < ApplicationController

  before_filter :authenticate_user!, :except => [:show, :index]

  def index
    @keynote = Keynote.find(params[:keynote_id])
    @stories = @keynote.stories
  end

  def new
    @keynote = Keynote.find(params[:keynote_id])
    @story = @keynote.stories.build
  end

  def create
    if user_signed_in?
      @keynote = Keynote.find(params[:keynote_id])
      @story = @current_user.keynote.stories.build(params[:story])
      if @story.save
        flash[:notice] = 'Question submission succeeded'
        redirect_to keynote_stories_path
      else
        render :action => 'new'
      end
    end
  end

This is my keynotes_controller.rb

class KeynotesController < ApplicationController
  def index
    @keynotes = Keynote.find :all, :order => 'id ASC'
  end

  def new
    @keynote = Keynote.new
  end

  def show
    @keynote = Keynote.find(params[:id])
  end

  def create
    @keynote = Keynote.new(params[:keynote])
    if @keynote.save
      flash[:notice] = 'Keynote submission succeeded'
      redirect_to keynotes_path
    else
      render :action => 'new'
    end
  end

end

These are the Parameters being passed:

{"utf8"=>"✓", "authenticity_token"=>"76odSpcfpTlnePxr+WBt36fVdiLD2z+Gnkxt/Eu1/TU=", "keynote_id"=>"1", "story"=>{"name"=>"Hi?"}, "commit"=>"Ask"}

And this is what I have in my routes.rb file:

  get "keynotes/index"
  get "users/show"
  devise_for :users
  get "votes/create"
  get "stories/index"

  resources :keynotes do
    resources :stories
  end

  resources :stories do
    get 'bin', :on => :collection
    resources :votes
  end

  resources :users

  root :to => 'keynotes#index'

I deleted current_userfrom the line with the error, leaving just @story = @keynote.stories.build(params[:story]). This throws a different error:

Routing Error

No route matches {:controller=>"stories"}

but the record in the database gets created, without the user_id though.

Any help would be really appreciated.

Update: here's my User model, user.rb

class User < ActiveRecord::Base

  has_many :stories
  has_many :keynotes
  has_many :votes
  has_many :stories_voted_on, :through => :votes, :source => :story

  # Include default devise modules. Others available are:
  # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me
end

Here's the keynote.rb model:

class Keynote < ActiveRecord::Base
  validates_presence_of :title, :description, :speaker, :location
  has_many :stories
  belongs_to :user
end

And here's the story.rb model:

class Story < ActiveRecord::Base
  after_create :create_initial_vote
  validates_presence_of :name
  belongs_to :user
  belongs_to :keynote

  has_many :votes do
    def latest
      find :all, :order => 'id DESC', :limit => 3
    end
  end

  protected
  def create_initial_vote
    votes.create :user => user
  end
end

回答1:

The error occurs because there is no keynote, but is keynotes. If you just want to add a Story to the User, remove the keynote.:

#return keynote even if it does not belong to current user
@keynote = Keynote.find(params[:keynote_id])
@story = @current_user.stories.build(params[:story]) #removed `keynote.` part
@story.keynote = @keynote #add the keynote to @story before saving @story

You should also be able to do:

#return the keynote id only if it belongs to current user
@keynote = @current_user.keynotes.find(params[:keynote_id])
@story = @keynote.stories.build(params[:story])

Because the keynote already belongs to a certain user. you should always scope your associations. So, instead of this:

 @keynote = Keynote.find(params[:keynote_id])

do:

 @keynote = @current_user.keynotes.find(params[:keynote_id])

This way, you know that you are always finding the keynote for current user. If a hacker send the param keynote_id = 55, but 55 does not belong to current_user, that means the hacker was able to associate a story with an probably-uninteded keynote.

I'm just guessing that you don't want to allow adding a story to a keynote belonging to some other user.