【问题标题】:How to use Rails 5's ActiveRecord attributes to provide a virtual column如何使用 Rails 5 的 ActiveRecord 属性提供虚拟列
【发布时间】:2019-02-21 15:02:25
【问题描述】:

我想为我的一些模型添加虚拟列,但要让它们的值由 ActiveRecord 语句(如Product.first)返回,以便我可以使用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,但是

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

仍然返回total: 0.0

我什至不确定attribute 是否是执行此操作的正确方法,因为文档似乎暗示它绑定到列。

总结一下:

  • products 表不应包含total 列。
  • 通过 ActiveRecord 访问 Product 应该返回一个 Product 对象,其中包含一个 total 键,该键具有基于模型其他属性的计算值。

这可能吗?

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

【问题讨论】:

  • 您在项目中使用 ActiveModelSerializers 吗?如果是,您可以使用total 方法创建一个单独的序列化程序并在所需的控制器操作中使用它

标签: ruby-on-rails ruby-on-rails-5 rails-activerecord


【解决方案1】:

您可以使用methods 选项

class Product < ApplicationRecord
  def total
    price + tax
  end
end

Product.first.to_json(methods: :total)

【讨论】:

  • 谢谢,但不幸的是,这仍然涉及更改每个 to_json 调用以添加方法。我希望得到与使用 attribute 相同的结果,当模型作为其不可分割的一部分被访问时它会返回 - 只是没有写入数据库。
  • @JohnY 或者您可以覆盖to_json 并自己添加:methods。当然,您需要小心处理从外部提供的:methods
【解决方案2】:

在您的模型中覆盖 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

【讨论】:

  • 谢谢——这似乎是最简洁的方法,因为它可以将所有内容保留在模型中。
【解决方案3】:

您数据库中的virtual/generated column(假设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 |

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

【讨论】:

  • 谢谢,但我确实说过我不想在数据库中使用那个虚拟的,只需要模型:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多