使用嵌套属性和单表继承保存/更新模型

时间:2012-04-05 17:15:20

标签: ruby-on-rails ruby-on-rails-3 nested-attributes single-table-inheritance

我有一个模型ModelRun,它接受​​另一个模型ParameterValue的嵌套属性。 (ModelRun has_many :parameter_values。)但是,ParameterValue还使用单表继承来保存两个子类:NumericParameterFileParameterFileParameter使用CarrierWave来存储文件。

问题在于ModelRunController保存或更新ModelRun时,默认情况下,@model_run.save@model_run.update_attributes无法识别ParameterValue属性的类型 - 它只是试图将它们存储为ParameterValue。这适用于NumericParameter值,但它会引发FileParameters的异常,因为CarrierWave uploader doesn't get mounted处理文件上载,因此在尝试将文件序列化到数据库时ActiveRecord失败。

处理此问题的最简洁方法是什么?我遇到的唯一解决方案是在控制器的@model_run.parameter_valuescreate方法中手动填充update集合,因为我可以告诉每个ParameterValue应该是哪种类型并创建正确的对象一个接一个。然而,这似乎重新实现了很多Rails魔法,因为我不能再使用ModelRun.new(params[:model_run])@model_run.update_attributes - 似乎它抛弃了在第一个中使用accepts_nested_attributes_for的大部分优势地点。有没有更好的方法,Rails Way™?

每个模型的相关部分都复制在下面。

model_run.rb

class ModelRun < ActiveRecord::Base
  has_many :parameter_values, dependent: :destroy

  accepts_nested_attributes_for :parameter_values, allow_destroy: true

  attr_accessible :name, 
                  :description, 
                  :geometry_description, 
                  :run_date, 
                  :run_date_as_string, 
                  :parameter_values_attributes
end

parameter_value.rb

class ParameterValue < ActiveRecord::Base
  belongs_to :model_run

  attr_accessible :type,
                  :parameter_id, 
                  :description, 
                  :numeric_value,
                  :model_run_id,
                  :parameter_file
end

numeric_parameter.rb

class NumericParameter < ParameterValue
  attr_accessible :numeric_value
end

file_parameter.rb

class FileParameter < ParameterValue
  mount_uploader :parameter_file, ParameterFileUploader

  attr_accessible :parameter_file
end

parameter_file_uploader.rb

class ParameterFileUploader < CarrierWave::Uploader::Base
  storage :file

  def store_dir
    "#{Rails.root}/uploads/#{model.class.to_s.underscore}/#{model.id}"
  end

  def cache_dir
    "#{Rails.root}/tmp/uploads/cache/#{model.id}"
  end

end

1 个答案:

答案 0 :(得分:4)

如果我理解你,你试图通过传递:type?来找到在STI层次结构中实例化正确子类的方便方法。如果以后不需要更改类型,可以将此hack添加到ParameterValue类

class ParameterValue < ActiveRecord::Base
    class << self
      def new_with_cast(*attributes, &block)
        if (h = attributes.first).is_a?(Hash) && !h.nil? && (type = h[:type] || h['type']) && type.length > 0 && (klass = type.constantize) != self
          raise "wtF hax!!"  unless klass <= self
          return klass.new(*attributes, &block)
        end

        new_without_cast(*attributes, &block)
      end
      alias_method_chain :new, :cast

    end
  end

在此之后,传递正确的类型将导致正确的ParameterValue实例化,包括上传,验证等。