首页 > 解决方案 > Rails 表单:如何存储(和检索)数组?

问题描述

我对 Rails 很陌生,想将 Rails 表单中的复选框值存储到数据库字段中的数组中。

我想在没有数据库关系/连接表的情况下执行此操作(表单值很少会改变,如果有的话),所以我在表单内的哈希中提供复选框选项。

使用这个答案作为指南,我已经完成了一些工作。

表单显示复选框选项,当我更新记录时,它确实在数据库字段中存储了一个包含选中值的数组(作为字符串)。但是,当我编辑记录时,表单中的复选框都是空白的。

我相信我需要告诉表单如何预先“检查”第一个参数中的适当框(:频率,这是数据库中的一个文本字段),但我不知道该怎么做。我试过了:

  1. :frequency[] 和其他变体
  2. 在 Building 模型中添加序列化

我已经看到在 Postgres 中使用 hstore 的参考资料,但我对此并不熟悉,也没有尝试过。

非常感谢...

使用:Rails 6.0.2.2、Postgres 12

_form.html.erb

<%= form_with(model: building, local: true) do |f| %>
...
<%= f.collection_check_boxes(:frequency, { 'Yearly': 1, 'Quarterly': 2, 'Monthly': 3, 'Weekly': 4 }, :last, :first) %>

building.rb

class Building < ApplicationRecord

  serialize :frequency, Array

Server log(检查表格中的第三个框后):

Processing by BuildingsController#update as HTML
  Parameters: {"authenticity_token"=>"p2fFVpKZnw5HroZvy3qGAbguTxf+3oEOjOrhEGTu4ImS7GpVwXrcj78XPt5ENcN6GQx9K9Cn/c3ZvMwOSqVYbQ==", "building"=>{"frequency"=>["", "3"]}, "commit"=>"Update Building", "id"=>"5"}

Building Update (0.8ms)  UPDATE "buildings" SET "frequency" = $1, "updated_at" = $2 WHERE "buildings"."id" = $3  [["frequency", "---\n- ''\n- '3'\n"], ["updated_at", "2020-04-05 17:56:22.142645"], ["id", 5]]
  ↳ app/controllers/buildings_controller.rb:40:in `update'
   (1.4ms)  COMMIT
  ↳ app/controllers/buildings_controller.rb:40:in `update'
Redirected to http://localhost:3000/buildings/5
Completed 302 Found in 14ms (ActiveRecord: 3.7ms | Allocations: 6210)

标签: arraysruby-on-railspostgresqlforms

解决方案


做什么都不要用serialize。它是来自黑暗时代的肮脏黑客,通过将数据序列化为 JSON 或 YAML 将复杂类型存储在 text/varchar 列中。它不应该与原生数组/json/hstore 类型一起使用,因为序列化是由数据库适配器处理的。如果你使用serialize数组列,你会得到类似["[1,2,3]"]插入数据库的东西,如果你使用 JSON,你会得到"{ \"foo\":\"bar\" }".

相反,如果您想使用位掩码数组,只需将该列创建为数组:

create_table :buildings do |t|
  t.integer 'frequency', array: true
end

数据库适配器将自动处理它与数组的转换。

如果您想使用 HStore 或 JSON,它们都可以完成哈希在 ruby​​ 中所做的工作:

create_table :buildings do |t|
  t.json 'frequency'
  # or
  t.hstore 'frequency'
end

但恕我直言,我会使用该程序并创建一个连接表。这不值得加入可以解决的大惊小怪和蹩脚的查询/问题。ActiveRecord 是围绕表和关系模型构建的。不是 hstore/array/json 列。

class Frequency
  has_many :building_frequencies
  has_many :frequencies, through: :building_frequencies
end

class BuildingFrequency
  belongs_to :building
  belongs_to :frequency
end

class Building
  has_many :building_frequencies
  has_many :frequencies, through: :building_frequencies
end

<%= f.collection_check_boxes(:frequency_ids, Frequency.all, :id, :name) %>

def building_params
  params.require(:building)
        .permit(
          :foo, :bar,
          frequency_ids: []
        )
end

它完成了工作,为您提供了参照完整性,并允许您使用内部连接来过滤频率上的行,这实际上比在数组/hstore 中插入要高效得多。


推荐阅读