Ruby on rails 验证外键约束

Ruby on rails 验证外键约束,ruby-on-rails,ruby,foreign-keys,Ruby On Rails,Ruby,Foreign Keys,我有两个表数据元素和类型。用于存储静态类型列表的类型,有两列:id和name数据元素具有类型列,该列应在类型表中引用id。当通过HTML表单创建DataElement时,类型列表显示为下拉列表,一切正常。我需要通过api调用创建DataElement,我想验证作为参数传递给REST调用的type实际上是存储在types表中的类型之一。我还没能想出办法。如有任何建议,我将不胜感激。模型方式(带验证) 如果您想在模型方面处理这个问题,我建议您创建一个“自定义属性”,这样您就不会影响现有类型字段的现有

我有两个表
数据元素
类型
。用于存储静态类型列表的类型,有两列:
id
name
<代码>数据元素具有
类型
列,该列应在
类型
表中引用
id
。当通过HTML表单创建
DataElement
时,类型列表显示为下拉列表,一切正常。我需要通过api调用创建
DataElement
,我想验证作为参数传递给REST调用的
type
实际上是存储在
types
表中的类型之一。我还没能想出办法。如有任何建议,我将不胜感激。

模型方式(带验证) 如果您想在模型方面处理这个问题,我建议您创建一个“自定义属性”,这样您就不会影响现有
类型
字段的现有功能。大致如下:

app/models/data\u 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
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
class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end
def create
  # Please add an index to `name` column and ensure is a unique one
  type = Type.find_by(name: params[:type].to_s)
  if type.nil?
    head :unprocessable_entity
  else
    @data_element = DataElement.new(data_element_params.merge(type: type))
    if @data_element.save
      #dosomething
    end
  end
end
class Type < ActiveRecord::Base
  has_many :data_elements
end
class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end
app/controllers/data\u elements\u controller.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
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
class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end
def create
  # Please add an index to `name` column and ensure is a unique one
  type = Type.find_by(name: params[:type].to_s)
  if type.nil?
    head :unprocessable_entity
  else
    @data_element = DataElement.new(data_element_params.merge(type: type))
    if @data_element.save
      #dosomething
    end
  end
end
class Type < ActiveRecord::Base
  has_many :data_elements
end
class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end
通过这种方式,您可以像处理属性一样处理
type_name
。您甚至不需要验证:如果没有找到该名称的
类型
,您只需不更改该值(您实际上可以报告错误,甚至在任何情况下都可以进行验证)

显然,如果你也使用
类型
,那么类似
@data\u element.update\u属性(type\u name:'foo',type:type.first)
你会得到奇怪的结果(
类型
赋值之后是
类型。first
而不是名为
'foo'
)的
类型,因此,如果您在作业中使用
type\u name
,请确保
type
在那一刻没有使用,但不应该是问题,只是不要将其放入
permit
参数中

控制器方式 我的第一个建议是“不要使用类型”作为表/模型名。
type
列通常由Rails用于单表继承,这是主要原因。即使将其命名为DataElementType也可以,但无论如何,这是您的选择

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

app/models/type.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
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
class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end
def create
  # Please add an index to `name` column and ensure is a unique one
  type = Type.find_by(name: params[:type].to_s)
  if type.nil?
    head :unprocessable_entity
  else
    @data_element = DataElement.new(data_element_params.merge(type: type))
    if @data_element.save
      #dosomething
    end
  end
end
class Type < ActiveRecord::Base
  has_many :data_elements
end
class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end
app/controllers/data\u elements\u controller.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
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
class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end
def create
  # Please add an index to `name` column and ensure is a unique one
  type = Type.find_by(name: params[:type].to_s)
  if type.nil?
    head :unprocessable_entity
  else
    @data_element = DataElement.new(data_element_params.merge(type: type))
    if @data_element.save
      #dosomething
    end
  end
end
class Type < ActiveRecord::Base
  has_many :data_elements
end
class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end
您可能想对该方法进行一点重构,但这肯定会给您提供一个总体思路。

我同意不建议使用
类型,因为它通常由STI使用。在您的情况下,如果您不想在
types
表中存储太多数据,我建议您定义一个常量,如
ELEMENT\u types
,它是一个数组,包含您将在
DataElement
模型中使用的所有类型。然后添加一个验证,如

class DataElement < ActiveRecord::Base
  validates :type, inclusion: { in: ELEMENT_TYPES }`
end
然后用
types\u container
helper方法替换
container
参数。如果要转换类型的值,这应该是您的选择。如果这是您所需要的,您可能需要了解rails中的一些东西。 希望我的回答能有所帮助

更新: 好吧,如果你坚持使用db来存储你的数据,这并不难满足你的需要。 我假设一个
类型
有许多
数据元素
,您的模式如下所示:

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
上添加索引或类似内容以提高查询效率)

首先,在数据元素和类型模型中定义关联关系:

app/models/type.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
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
class Type
  validates :name, presence: true, uniqueness: { case_sensitive: false }

end
def create
  # Please add an index to `name` column and ensure is a unique one
  type = Type.find_by(name: params[:type].to_s)
  if type.nil?
    head :unprocessable_entity
  else
    @data_element = DataElement.new(data_element_params.merge(type: type))
    if @data_element.save
      #dosomething
    end
  end
end
class Type < ActiveRecord::Base
  has_many :data_elements
end
class DataElement < ActiveRecord::Base
  belongs_to :type, foreign_key: 'de_type'
end
如果
type
表的id集合中不存在
de_type
,则此验证将使更新或创建失败

我将在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">
]> 
如您所见,我试图创建一个数据元素,其
de\u type
7。你找不到id为7的
类型
记录,对吗?因此,您最终看到出现了RecordInvalid错误,错误信息为
类型不能为空

现在,让我们创建一个没有任何错误的数据元素:

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"> 
2.1.1:010>DataElement.create!(德鲁类型:2)
(0.1ms)开始事务处理
类型加载(131.0ms)从“类型”中选择“类型”。*其中“类型”。“id”=?限制1[[“id”,2]]
SQL(1.1ms)插入“数据元素”(“数据类型”、“创建时间”、“更新时间”)值(?,?)[“数据类型”,2],“创建时间”,“2015-07-08 03:05:51.046949”],[“更新时间”,“2015-07-08 03:05:51.046949”]
(6.3ms)提交事务
=> # 
那么,记录被成功创建了,有趣吗?
您可以在rails指南中找到更多信息(很抱歉,我不能发布超过2个链接)

谢谢!列的实际名称为de_类型。我想我在描述模型时尽量保持简单。这绝对有帮助。我在想,一定有一种方法可以让它通过使用“验证”或仅仅通过定义对象之间的关系(有一个或属于)来自动工作。我想不是。我会按照你的建议去做。有一种方法是肯定的,不是完全自动的,但会使用
验证
,让我更新答案;)@火龙多尔,伊芙梦想家,你们真是太棒了!非常感谢。我和Ruby打交道才一个星期,所以我非常感谢所有额外的细节。现在,为什么我的名字是“user3038783”?我最好改一下:-)谢谢。您是否建议使用常量元素类型,而不是将其存储在表中或用表中的数据填充。?我更希望它存储在数据库中,以防它被其他东西引用。