如何使用Rails5的ActiveRecord属性提供虚拟列

How to use Rails 5's ActiveRecord attributes to provide a virtual column

我想将虚拟列添加到我的一些模型中,但要让它们的值由像 Product.first 这样的 ActiveRecord 语句 return 编辑,这样我就可以使用像 Product.first.to_json 这样的语句根据 API 请求输出带有虚拟列的产品。

列的值取决于其他模型属性。我希望这些列保存到数据库中。

我试过这个:

class Product < ApplicationRecord
  def total
    price + tax
  end
end

Product.first没有包括总数。

class Product < ApplicationRecord
  attribute :total, :decimal, default: -> { 0.0 }
end

total: 0.0 添加到 returned 对象,但是

class Product < ApplicationRecord
  attribute :total, :decimal, default: -> { price + tax }
end

失败并显示

等消息
#<NameError: undefined local variable or method `price' for #<Class:0x0000557b51c2c960>>

class Product < ApplicationRecord
  attribute :total, :decimal, default: -> { 0.0 }

  def total
    price + tax
  end
end

仍然returns total: 0.0.

我什至不确定 attribute 是否是正确的方法,因为文档似乎暗示它绑定到一个列。

总结一下:

这可能吗?

真的 不想用大量手动插入这些虚拟列的代码替换每个 to_json 调用…

您可以使用 methods 选项

class Product < ApplicationRecord
  def total
    price + tax
  end
end

Product.first.to_json(methods: :total)

覆盖模型中的 as_json 以包含您的方法。

这不会在您检索到的 Product 对象中包含总计,但会在对该对象调用 .to_json 时包含总计。

class Product < ApplicationRecord
  attribute :total, :decimal, default: -> { 0.0 }

  def total
    price + tax
  end

  def as_json(options = {})
    super(methods: [:total])
  end
end

A virtual/generated column (assuming MySQL/MariaDB) 在你的数据库中将解决你所需要的。因为它是从其他列的数据生成的,所以您不能写入它,它只能在读取操作期间更新。可以选择保留数据,但这不是这里的问题。

在我的示例中,我想向我的 People 数据库添加一个虚拟列 "age",这是 person.birthday 和 curdate() 之间的区别。 我生成列:

rails generate migration AddAgeToPeople age:virtual

然后我编辑迁移文件,使 add_column :people, :age, :virtual 变成

class AddAgeToPeople < ActiveRecord::Migration[5.2]
  def change
    add_column :people, :age, :int, as: "timestampdiff(year, birthday, curdate())"
  end
end

最终结果将是 SQL,看起来像:

ALTER TABLE people ADD COLUMN age GENERATED ALWAYS AS (timestampdiff(year, birthday, curdate()));

| Field | Type | Null | Key | Default | Extra | | age | int(11) | YES | | NULL | VIRTUAL GENERATED |

最终结果是模型中的一个属性,我可以正常交互(虽然只读)