2

The task was allow user to view, create, edit records in different units, i.e. altitude in meters and feets, speed in m/s, knots, km/h, mi/h.

I've read a lot about Value Objects, composed_of and why we should not use it and use serialize instead and came with solution below.

It seems like complex solution for me. Could you please point me what can I refactor and direction for it?

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 is almost the same as distance.

Aleksandr K.
  • 1,338
  • 14
  • 21

0 Answers0