You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Rails 5.1应用中如何在PostgreSQL存储加密货币数据?选型及gem疑问

加密货币资产追踪应用:PostgreSQL存储与rails-money扩展方案

一、PostgreSQL中存储加密货币价值的字段类型选择

首先明确:绝对不要用float/double类型,这类浮点类型存在精度丢失问题,对于加密货币这种需要精确到小数点后8位甚至更多的场景来说完全不可行。

推荐使用PostgreSQL的numeric类型,在Rails模型中对应的是decimal字段。具体配置时,建议设置足够的精度和小数位数以覆盖主流加密货币的数值范围:

  • 精度(precision):设为18,足以容纳大部分加密货币的最大市值对应的数值
  • 小数位数(scale):设为8,比特币、以太坊等主流币种均支持8位小数,小众币种也基本不会超过这个范围

在Rails 5.1的迁移文件中可以这样定义:

class CreateCryptoAssets < ActiveRecord::Migration[5.1]
  def change
    create_table :crypto_assets do |t|
      t.decimal :value, precision: 18, scale: 8, null: false
      t.string :currency, null: false # 存储币种标识,比如BTC、ETH、BNB
      # 其他业务字段:用户关联ID、持有数量、最新更新时间等
      t.timestamps
    end
  end
end

选择numeric/decimal的核心原因是它属于精确数值类型,能完整存储所有小数位,不会出现浮点运算的精度误差,完全匹配加密货币这类金融数据的存储要求。

二、扩展rails-money gem支持多币种

你提到rails-money默认只支持BTC,其实这个gem是完全支持自定义添加任意币种的,只需要在项目初始化文件中配置即可:

  1. 新建或修改config/initializers/money.rb文件,添加自定义币种配置:
# 添加以太坊配置
Money::Currency.add(
  id: :eth,
  priority: 1,
  symbol: 'Ξ',
  name: 'Ethereum',
  symbol_first: true,
  subunit: 'Wei',
  subunit_to_unit: 10**18, # 1ETH = 10^18 Wei(以太坊最小单位)
  decimal_mark: '.',
  thousands_separator: ','
)

# 添加币安币配置
Money::Currency.add(
  id: :bnb,
  priority: 2,
  symbol: 'BNB',
  name: 'Binance Coin',
  symbol_first: true,
  subunit: 'Wei',
  subunit_to_unit: 10**18,
  decimal_mark: '.',
  thousands_separator: ','
)

你可以根据需求添加任意加密货币的配置,关键是subunit_to_unit要对应该币种的最小单位转换比例(比如BTC的最小单位是聪,1BTC=10^8聪,所以这里填10**8)。

  1. 在模型中结合rails-money封装业务逻辑:
    如果想直接利用rails-money的工具方法(比如格式转换、金额计算),可以调整迁移为存储最小单位数值+币种:
# 修改迁移文件
t.integer :value_cents, null: false # 存储最小单位数值,比如1BTC对应100000000
t.string :currency, null: false

# 模型中关联rails-money
class CryptoAsset < ApplicationRecord
  monetize :value_cents, with_model_currency: :currency
end

如果想保留之前的decimal字段,也可以手动封装money对象:

class CryptoAsset < ApplicationRecord
  def value_money
    Money.new(value * currency_subunit_to_unit, currency)
  end

  private

  def currency_subunit_to_unit
    Money::Currency.find(currency).subunit_to_unit
  end
end

三、额外最佳实践

  • 把支持的币种维护在常量或枚举中,避免硬编码:
class CryptoAsset < ApplicationRecord
  SUPPORTED_CURRENCIES = %w[BTC ETH BNB].freeze
  validates :currency, inclusion: { in: SUPPORTED_CURRENCIES }
end
  • 如果后续需要频繁新增币种,建议建一个currencies表存储币种配置信息,再关联到crypto_assets表,扩展性更强。

内容的提问来源于stack exchange,提问作者user993460

火山引擎 最新活动