验证外键约束

时间:2015-07-07 16:41:03

标签: ruby-on-rails ruby foreign-keys

我有两张表data_elementstypes。用于存储静态类型列表的类型,包含两列:idnamedata_elementstype列,应在id表中引用types。当通过HTML表单创建DataElement时,类型列表显示为下拉列表,一切正常。我需要通过api调用创建DataElement,并且我想验证作为REST调用的参数传递的type实际上是存储在types表中的类型之一。我无法弄清楚如何做到这一点。我很感激任何建议。

2 个答案:

答案 0 :(得分:0)

模型方式(带验证)

如果您想在模型方面处理此问题,建议您创建“自定义属性”,这样您就不会影响现有type字段的现有功能。这些方面的东西:

应用/模型/ data_element.rb

def type_name=(value)
  found_type = Type.find_by(name: value.to_s)
  self.type  = found_type unless found_type.nil?
end

def type_name
  type.try(:name)
end

应用/控制器/ data_elements_controller.rb

def create
  @data_element = DataElement.new(data_element_params)
  @data_element.save
end

def data_element_params
  # Please ensure that `type` is not present in `permit`
  params.require(:data_element).permit(:whatever, :type_name, :other_field)
end

通过这种方式,您可以处理type_name,就像它是属性一样。您甚至不需要验证:如果找不到该名称的Type,您只需更改该值(实际上您可以报告错误甚至验证)。

显然,如果您也使用type,那么@data_element.update_attributes(type_name: 'foo', type: Type.first)之类的内容会导致奇怪的结果(type之后会发生Type.first分配,因此您最终会{{1}而不是名为Type的{​​{1}},所以如果您在分配中使用'foo',请确保在此时间内不使用type_name,但不应该是问题,只是不要把它放在type参数

控制器方式

我的第一个建议是“不要使用类型”作为表/型号名称。 Rails单表继承通常使用permit列,这是主要原因。即使将其称为DataElementType也已经可以,但无论如何这都是您的选择。

关于你的问题,有多种方法可以做到这一点,我想到的第一个方法是

应用/模型/ type.rb

type

应用/控制器/ data_elements_controller.rb

class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end

你可能想稍微重构一下这个方法,但这肯定会给你一个大概的想法。

答案 1 :(得分:0)

我同意@Fire-Dragon-DoL建议不要使用type,因为它通常由STI使用。在您的情况下,如果您不在types表中存储太多数据,我建议您定义一个类似ELEMENT_TYPES的常量,这是一个包含您在DataElement模型中使用的所有类型的数组。然后添加类似

的验证
class DataElement < ActiveRecord::Base
  validates :type, inclusion: { in: ELEMENT_TYPES }`
end

如果是这样,您可以避免在保存data_element对象时编写冗余逻辑来验证type属性。

您可以使用options_for_select(container, selected = nil)方法完成选择框。如果选项值和选项名称相同,那么您应该只使用container替换DataElement::ELEMENT_TYPES参数。如果没有,您最好定义一个辅助方法,如:

def types_container
  DataElement::ELEMENT_TYPES.map do |type|
    [DataElement.human_attribute_name(type), type]
  end
end

然后只需用container辅助方法替换types_container参数。如果您要翻译类型的值,这应该是您的选择。如果这是您需要的,您可能必须在rails中知道名为I18n的内容。 希望我的回答可以提供帮助。

<强>更新 好吧,如果你坚持使用db存储你的数据,就不难满足需要。 我认为type有很多data_elements,您的架构看起来像:

ActiveRecord::Schema.define(version: 20150708021148) do

  create_table "data_elements", force: :cascade do |t|
    t.integer  "de_type"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "types", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

(您可能需要在de_type上添加索引或类似内容以提高查询效率)

首先,在DataElement和Type模型中定义关联关系:

应用/模型/ type.rb

class Type < ActiveRecord::Base
  has_many :data_elements
end

应用/模型/ data_element.rb

class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end

这样,您就可以轻松通过type获取data_element个对象,或通过data_elements获取type

然后您只需要向DataElement模型添加验证:

validates :type, presence: true

如果de_type表的id集合中不存在types,则此验证将无法通知更新或创建。

我将在rails控制台中向您展示它的工作原理:

2.1.1 :006 >   Type.all
=> #<ActiveRecord::Relation [
#<Type id: 1, name: "type1", created_at: "2015-07-08 02:17:33", updated_at: "2015-07-08 02:17:33">, 
#<Type id: 2, name: "type2", created_at: "2015-07-08 02:17:38", updated_at: "2015-07-08 02:17:38">, 
#<Type id: 3, name: "type3", created_at: "2015-07-08 02:17:39", updated_at: "2015-07-08 02:17:39">, 
#<Type id: 4, name: "type4", created_at: "2015-07-08 02:17:41", updated_at: "2015-07-08 02:17:41">, 
#<Type id: 5, name: "type5", created_at: "2015-07-08 02:17:43", updated_at: "2015-07-08 02:17:43">
]> 

你可以发现我为types创建了一些记录。现在我将尝试创建一些data_elements:

2.1.1 :009 >   DataElement.create!(de_type: 7)

ActiveRecord::RecordInvalid: Validation failed: Type can't be blank

嗯,正如您所见,我尝试创建一个de_type 7 的data_element。你找不到id为7的type记录,对吗?所以你终于看到引发了RecordInvalid错误,错误信息是Type can't be blank

现在,让我们创建一个没有任何错误的data_element:

2.1.1 :010 > DataElement.create!(de_type: 2)
   (0.1ms)  begin transaction
  Type Load (131.0ms)  SELECT  "types".* FROM "types" WHERE "types"."id" = ? LIMIT 1  [["id", 2]]
  SQL (1.1ms)  INSERT INTO "data_elements" ("de_type", "created_at", "updated_at") VALUES (?, ?, ?)  [["de_type", 2], ["created_at", "2015-07-08 03:05:51.046949"], ["updated_at", "2015-07-08 03:05:51.046949"]]
   (6.3ms)  commit transaction
 => #<DataElement id: 4, de_type: 2, created_at: "2015-07-08 03:05:51", updated_at: "2015-07-08 03:05:51"> 

那么,记录是否成功创建,有意思? 您可以在rails guide中找到更多信息(抱歉,我无法发布超过2个链接)