Rails 3 - nil的未定义方法`map':NilClass用于我自己的验证

Exi*_*iRe 2 ruby ruby-on-rails ruby-on-rails-3

我遇到了很奇怪的问题.我有Timetable模型,并尝试编写我的自定义验证.所以,现在我只是想为字段添加测试错误,以确保一切正常.但它不起作用.所以,我尝试更新时间表模型的对象,但是当我不使用我的测试自定义验证时,一切都很完美.否则我得到这样的错误:

NoMethodError in Timetables#update 

undefined method `map' for nil:NilClass

32: 
33:       <div class="controls">
34:         <%= f.select( :curriculum_id,                                                           
35:                       options_for_select( @subjects_with_curriculums,
36:                                           @tt.curriculum_id ),
37:                       { :include_blank => true }) %>
38:       </div>
Run Code Online (Sandbox Code Playgroud)

这是我的模型:

# == Schema Information
#
# Table name: timetables
#
#  id                  :integer         not null, primary key
#  curriculum_id       :integer
#  school_class_id     :integer
#  tt_day_of_week      :string(255)
#  tt_number_of_lesson :integer
#  tt_room             :string(255)
#  tt_type             :string(255)
#  created_at          :datetime        not null
#  updated_at          :datetime        not null
#

class Timetable < ActiveRecord::Base
  belongs_to :curriculum
  belongs_to :school_class

  has_many :lessons

  validates :school_class_id, :presence => { :message => "should exist" }

  validates :tt_day_of_week,
              :presence  => true,
              :inclusion => { :in => %w(Mon Tue Wed Thu Fri) }

  validates :tt_number_of_lesson,
              :presence => true,
              :inclusion => {
                              :in => 1..9,
                              :message => "should have 1..9 symbols"
                            }

  validates :tt_room,
              :length => {
                           :maximum => 3,
                           :message => "should have 3 symbols"
                         },
              :allow_blank => true

  validates :tt_type,
              :inclusion => { :in => ["Primary lesson", "Extra"] },
              :allow_blank => true

  validate :test

  def test
    errors.add(:tt_number_of_lesson, "test")
  end
end 
Run Code Online (Sandbox Code Playgroud)

我的控制器:

# encoding: UTF-8
class TimetablesController < ApplicationController
  ...

  def edit
    @types_of_lesson = collect_types_of_lesson
    @tt = Timetable.find( params[:id] )
    @subjects_with_curriculums = collect_subjects_with_curriculums( @tt.school_class )
  end

  def update
    @tt = Timetable.find( params[:id] )

    if @tt.update_attributes( params[:timetable] )
      flash[:success] = "?????????? ??????? ?????????!"
      redirect_to timetables_path
    else
      flash.now[:error] = @tt.errors.full_messages.to_sentence :last_word_connector => ", ",
                                                               :two_words_connector => ", "
      render 'edit'
    end
  end

  private
    # Collecting subjects names for school class and curriculum_id for each subject.
    def collect_subjects_with_curriculums( school_class )
      subjects = school_class.curriculums.collect do |c|
        [ c.qualification.subject.subject_name, c.id  ]
      end
    end

    def timetable_for_class_with_existance_data( school_class )
      return [] if Timetable.all.empty?

      Timetable.select do |t|
        ( t.school_class.class_code == school_class.class_code ) and
        not ( t.tt_room.blank? ) and not ( t.tt_type.blank? ) and
        not ( t.curriculum_id.nil? )
      end.to_a
    end

    # Return for school class it's timetable.
    def timetable_for_class( school_class )
      Timetable.select{|t| t.school_class.class_code == school_class.class_code }.to_a
    end

    def subjects_of_class( school_class )
      subjects = school_class.curriculums.collect do |c|
        c.qualification.subject.subject_name
      end
    end

    # Return sorted by number of lesson tometable for one day.
    def sorted_timetable_for_day( timetable, day )
      timetable.select{ |t| t.tt_day_of_week == day }
               .sort_by{ |e| e[:tt_number_of_lesson] }
    end

    # Return russian name for type of lesson.
    def collect_types_of_lesson
      [ ["??????????? ???????", "Primary lesson"], ["???????", "Extra"] ]
    end

    # Check if timetable already has been created for school class.
    def timetable_exists?( school_class )
      not timetable_for_class( school_class ).empty?
    end
end
Run Code Online (Sandbox Code Playgroud)

我的看法

<%= form_for @tt, :html => {:class => "form-horizontal"} do |f| %>
  <%= field_set_tag do %>

    <%= f.hidden_field :tt_number_of_lesson %>
    <%= f.hidden_field :tt_day_of_week %>
    <%= f.hidden_field :school_class_id %>

    <div class="control-group">
       <%= f.label :tt_day_of_week, "Day of the week", :class => "control-label" %>

       <div class="controls">
         <%= content_tag( :span, translate_day_of_week( @tt.tt_day_of_week ),
                          :class =>"input-xlarge uneditable-input span2" ) %>
       </div>
    </div>

    <div class="control-group">
       <%= f.label :tt_number_of_lesson, "Number of lesson", :class => "control-label" %>

       <div class="controls">
         <%= content_tag( :span, @tt.tt_number_of_lesson,
                          :class =>"input-xlarge uneditable-input span1" ) %>
       </div>
    </div>

    <hr/>

    <div class="control-group">
      <%= f.label :curriculum_id, "Type of subject", :class => "control-label" %>

      <div class="controls">
        <%= f.select( :curriculum_id,                                                           
                      options_for_select( @subjects_with_curriculums,
                                          @tt.curriculum_id ),
                      { :include_blank => true }) %>
      </div>
    </div>

    <div class="control-group">
      <%= f.label :tt_room, "Code of the room", :class => "control-label" %>

      <div class="controls">
        <%= f.text_field :tt_room, :class => "span2", :maxlength => 3 %>
      </div>
    </div>

    <div class="control-group">
      <%= f.label :tt_type, "Type of the lesson", :class => "control-label" %>

      <div class="controls">
        <%= f.select( :tt_type,                                                           
                      options_for_select( @types_of_lesson,
                                          @tt.tt_type ),
                      { :include_blank => true }) %>
      </div>
    </div>

    <%= f.submit "Update", :class => "btn btn-large btn-warning" %>
  <% end %>
<% end %>  
Run Code Online (Sandbox Code Playgroud)

当我删除:

<div class="control-group">
  <%= f.label :curriculum_id, "Type of subject", :class => "control-label" %>

  <div class="controls">
    <%= f.select( :curriculum_id,                                                           
                  options_for_select( @subjects_with_curriculums,
                                      @tt.curriculum_id ),
                  { :include_blank => true }) %>
  </div>
</div>

<div class="control-group">
  <%= f.label :tt_type, "Type of the lesson", :class => "control-label" %>

  <div class="controls">
    <%= f.select( :tt_type,                                                           
                  options_for_select( @types_of_lesson,
                                      @tt.tt_type ),
                  { :include_blank => true }) %>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

我可以查看我的测试错误.我无法想象发生了什么.

mu *_*ort 6

你在update控制器中这样说:

render 'edit'
Run Code Online (Sandbox Code Playgroud)

这只是告诉Rails渲染编辑模板,它不运行任何与edit控制器关联的代码,它只是edit.html.erb在当前上下文中呈现.

Rails指南中布局和渲染:

2.2.2渲染动作的观点
[一些东西有关render 'x',render :xrender :action => :x是相同的...]

使用renderwith :action是Rails新手的常见混淆源.指定的操作被用于确定呈现哪些视图,但导轨并没有在控制器操作来运行的任何代码.必须在调用之前在当前操作中设置视图中所需的任何实例变量render.

特别注意最后一句话:

必须在调用之前在当前操作中设置视图中所需的任何实例变量render.

你设置@subjects_with_curriculumsedit控制器,但是使用edit从视图update控制器.其结果是,@subjects_with_curriculumsnil时候edit.html.erb尝试使用它时,update试图渲染编辑视图.

您需要@subjects_with_curriculumsupdate控制器中进行设置,您可能也需要@types_of_lesson.