ActiveRecord属性的实现值对象模式

时间:2016-06-15 10:37:21

标签: ruby-on-rails ruby activerecord value-objects

该任务允许用户以不同的单位查看,创建,编辑记录,即以米和英尺为单位的高度,以米/秒为单位的速度,结,公里/小时,英里/小时。

我已经阅读了很多关于值对象的内容,composed_of以及为什么我们不应该使用它并使用serialize来代替下面的解决方案。

对我来说,这似乎是一个复杂的解决方案。 你能指点一下我能为它重构和指导吗?

app/models/weather.rb

class Weather < ActiveRecord::Base
  attr_accessor :altitude_unit, :wind_speed_unit
  attr_reader :altitude_in_units, :wind_speed_in_units

  belongs_to :weatherable, polymorphic: true

  before_save :set_altitude, :set_wind_speed

  validates_presence_of :actual_on, :wind_direction
  validate :altitude_present?
  validate :wind_speed_present?

  validates_numericality_of :altitude, greater_than_or_equal_to: 0, allow_nil: true
  validates_numericality_of :altitude_in_units, greater_than_or_equal_to: 0, allow_nil: true
  validates_numericality_of :wind_speed, greater_than_or_equal_to: 0, allow_nil: true
  validates_numericality_of :wind_speed_in_units, greater_than_or_equal_to: 0, allow_nil: true
  validates_numericality_of :wind_direction, greater_than_or_equal_to: 0, less_than: 360, allow_nil: true

  serialize :altitude, Distance
  serialize :wind_speed, Velocity

  def wind_speed_in_units=(value)
    @wind_speed_in_units = value_from_param(value)
  end

  def altitude_in_units=(value)
    @altitude_in_units = value_from_param(value)
  end

  private

  def value_from_param(value)
    return nil if value.is_a?(String) && value.empty?
    value
  end

  def altitude_present?
    return if altitude.present? || altitude_in_units.present?
    errors.add :altitude, :blank
  end

  def wind_speed_present?
    return if wind_speed.present? || wind_speed_in_units.present?
    errors.add :wind_speed, :blank
  end

  def set_altitude
    return if altitude_in_units.blank? || altitude_unit.blank?
    self.altitude = Distance.new(altitude_in_units, altitude_unit)
  end

  def set_wind_speed
    return if wind_speed_in_units.blank? || wind_speed_unit.blank?
    self.wind_speed = Velocity.new(wind_speed_in_units, wind_speed_unit)
  end
end

app/model/distance.rb

class Distance < DelegateClass(BigDecimal)

  FT_IN_M = 3.280839895

  def self.load(distance)
    new(distance) unless distance.nil?
  end

  def self.dump(obj)
    obj.dump
  end

  def initialize(distance, unit = 'm')
    value = convert_from(BigDecimal.new(distance), unit)
    super(value)
  end

  def dump
    @delegate_dc_obj
  end

  def convert_to(unit)
    method = "to_#{unit}"
    raise ArgumentError, "Unsupported unit #{unit}" unless respond_to? method
    send method
  end

  def convert_from(val, unit)
    method = "from_#{unit}"
    raise ArgumentError, "Unsupported unit #{unit}" unless respond_to? method
    send method, val
  end

  def to_m
    @delegate_dc_obj
  end

  def to_ft
    @delegate_dc_obj * FT_IN_M
  end

  def from_m(val)
    val
  end

  def from_ft(val)
    val / FT_IN_M
  end
end

app/models/velocity.rb与距离几乎相同。

0 个答案:

没有答案