【问题标题】:Ruby: Object not instantiated by new method with an initialize methodRuby:对象未通过具有初始化方法的新方法实例化
【发布时间】:2014-11-30 17:54:58
【问题描述】:

用例:上传和处理一个 csv 文件,并在数据库中记录上传会话。

方法:创建一个模型来保存有关上传会话的数据,并创建一个控制器,其中包含一个收集 csv 文件的新方法和一个创建、填充和保存上传对象的 create 方法。

问题:模型包含一个初始化方法(参见模型代码),该方法似乎具有初始化对象所需的信息(参见调试输出),但是,当控制器尝试使用模型的新方法时,该尝试没有产生一个有用的对象(参见控制器代码和调试输出)。

问题:创建 call_history_upload 对象需要进行哪些更改?

Ruby 1.9.3 on Rails 3.2.13 on Windows 8.1

型号代码:

class CallHistoryUpload < ActiveRecord::Base
  attr_accessible :file_name, :user_id, :record_count, :international_call_count, :unknown_client_count

  has_many :call_histories, dependent: :destroy

  require 'csv'

  def initialize( file_name, user_id )
    logger.debug( "CallHistoryUpload.initialize start")
    logger.debug( "  call_history_file  = " + file_name )
    logger.debug( "  user_id            = " + user_id )
    @file_name = file_name
    @user_id = user_id
    @record_count = 0
    @international_call_count = 0
    @unknown_client_count = 0
    logger.debug( "CallHistoryUpload.initialize end")
  end
end

控制器代码:

class CallHistoryUploadsController < ApplicationController

  def get_client_id
    -1
  end

  # GET /call_history_uploads/new
  # GET /call_history_uploads/new.json
  def new
    @call_history_upload = CallHistoryUpload.new( "Unknown", session[:user_id] ) # Needed to suppoprt json

    respond_to do |format|
     format.html # new.html.erb
      format.json { render json: @call_history_upload }
    end
  end

  # POST /call_history_uploads
  # POST /call_history_uploads.json
  def create
    logger.debug( "CallHistoryUploadsController start")
    @call_history_upload = CallHistoryUpload.new( params[:call_history_file].original_filename, session[:user_id] )
    logger.debug( "CallHistoryUploadsController after instantiation")
<<This is line 24>>logger.debug( "call_history_upload.file_name = " + @call_history_upload.file_name )

    respond_to do |format|
      if @call_history_upload.save
        format.html { redirect_to @call_history_upload, notice: 'Call history upload was successful.' }
        format.json { render json: @call_history_upload, status: :created, location: @call_history_upload }
      else
        format.html { render action: "new" }
        format.json { render json: @call_history_upload.errors, status: :unprocessable_entity }
      end
    end
  end 
end

日志输出:

Started POST "/call_history_uploads" for 127.0.0.1 at 2014-11-30 08:40:27 -0500
Processing by CallHistoryUploadsController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"iDugtKa8b/U9q71Gk86sJMK6hnX1gvgQt496PX2q4Oo=", "call_history_file"=>#<ActionDispatch::Http::UploadedFile:0x4717788 @original_filename="Test_kiyvivzokpysxilfoftavyuftot.csv", @content_type="application/vnd.ms-excel", @headers="Content-Disposition: form-data; name=\"call_history_file\"; filename=\"Test_kiyvivzokpysxilfoftavyuftot.csv\"\r\nContent-Type: application/vnd.ms-excel\r\n", @tempfile=#<File:C:/Users/Gene/AppData/Local/Temp/RackMultipart20141130-5144-yn8i9>>, "commit"=>"Submit"}
CallHistoryUploadsController start
CallHistoryUpload.initialize start
  call_history_file  = Test_kiyvivzokpysxilfoftavyuftot.csv
  user_id            = KinteraAdmin
CallHistoryUpload.initialize end
CallHistoryUploadsController after instantiation
Completed 500 Internal Server Error in 0ms

NoMethodError (undefined method `has_key?' for nil:NilClass):
  app/controllers/call_history_uploads_controller.rb:24:in `create'


  Rendered C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_trace.erb (0.0ms)
  Rendered C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (0.0ms)
  Rendered C:/Ruby193/lib/ruby/gems/1.9.1/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (0.0ms)

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    天哪,真是一团糟……您应该使用组合而不是继承。第一次重构可能是:

    class CallHistoryUpload < SimpleDelegator
      class Model < ActiveRecord::Base
        has_many :call_histories, dependent: :destroy
      end
    
      def initialize(file_name, user_id)
        logger.debug( 'CallHistoryUpload.initialize start')
        logger.debug( "  call_history_file  = #{file_name}" )
        logger.debug( "  user_id            = #{user_id}" )
        super( new_model(file_name, user_id) )
        logger.debug( "CallHistoryUpload.initialize end")
      end
    
      def model
        __getobj__
      end
    
      def self.create(file_name, user_id)
        logger.debug('CallHistoryUploadsController start')
        chu = self.new(file_name, user_id)
        logger.debug( 'CallHistoryUploadsController after instantiation')
        logger.debug( "call_history_upload.file_name = #{chu.file_name}")
    
        return chu
      end
    
      private
      def new_model(file_name, user_id)
        self.class::Model.new({
          file_name: file_name,
          user_id: user_id,
          record_count: 0,
          international_call_count: 0,
          unknown_client_count: 0,
        })
      end
    end
    

    在控制器中我们只改变创建:

      # POST /call_history_uploads
      # POST /call_history_uploads.json
      def create
        @call_history_upload = CallHistoryUpload.create( params[:call_history_file].original_filename, session[:user_id] )
    
        # rest of the code
    

    如果您使用form_for 之类的东西,您可能需要将@call_history_upload.model 传递给它。

    始终尝试在控制器中放置尽可能少的逻辑并将其封装在某个对象中:)

    【讨论】:

    • 嗯。感谢收看。
    【解决方案2】:

    部分问题似乎是您在初始化时覆盖了ActiveRecord::Base 的正常行为。活动记录对象允许您填充对象的属性,例如 MyObject.new(property: 'value')

    从您的对象中删除initialize 方法并使用实例化您的对象

    file_name = params[:call_history_file].original_filename 
    user_id = session[:user_id]
    CallHistoryUpload.new(file_name: file_name, user_id: user_id)
    

    在您的初始化方法中调用 super 以允许 ActiveRecord::Base 这样做

    def initialize(file_name, user_id)
      logger.debug( "CallHistoryUpload.initialize start")
      logger.debug( "  call_history_file  = " + file_name )
      logger.debug( "  user_id            = " + user_id )
      @record_count = 0
      @international_call_count = 0
      @unknown_client_count = 0
      super(file_name: file_name, user_id: user_id)
      logger.debug( "CallHistoryUpload.initialize end")
    end
    

    我推荐第一种方法,因为它可以清理您的模型。如果您需要在模型实例上分配默认值,我建议创建一个具有描述性名称的类方法,该方法在调用 new 时设置此值,执行与第一种方法相同的分配。使用after_initialize 回调也是设置默认值的另一种选择,但请注意其后果。

    如果您是新手并且正在尝试习惯 Rails 约定,我真的建议您阅读 Rails 入门指南。

    【讨论】:

    • 感谢您的关注和您的帮助。我是 Rails 的新手。但是,我已经阅读了 Rails 入门和其他 Rails 指南 - 显然没有所需的效果。我编写的代码遵循在 stackoverflow.com/questions/13216976/… 找到的对 stackoverflow 问题 13216976 的响应。
    • 我试过你的建议,效果很好。谢谢!您对我使用的参考资料的任何想法都会对我的学习有所帮助。
    • 您看到的资源在仅使用 Ruby 语言时似乎有一个很好的答案。但是,当您添加您的类以从 ActiveRecord::Base 继承时,您必须知道它在幕后做了什么。它不仅仅是将实例变量分配给您的对象。如果您想了解它是如何做到的,您必须深入研究 Rail 的 ActiveRecord 代码并阅读文档。这是一种很好的学习方式。
    猜你喜欢
    • 2023-03-14
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-04
    相关资源
    最近更新 更多