Class: Shipping::Attribute
- Inherits:
-
Object
- Object
- Shipping::Attribute
- Includes:
- Comparable
- Defined in:
- app/services/shipping/package.rb
Overview
Base class for typed physical measurements (length, mass, etc.).
Instance Attribute Summary collapse
-
#amount ⇒ Object
readonly
Returns the value of attribute amount.
-
#unit ⇒ Object
readonly
Returns the value of attribute unit.
Class Method Summary collapse
-
.add_conversion(multiple_unit, other_unit, multiple) ⇒ void
Stores a bidirectional conversion factor between two units.
-
.add_conversion_method_for(sym, _options = {}) ⇒ void
Defines instance conversion methods for the given unit.
- .add_methods_for(sym, options = {}) ⇒ void
-
.add_numeric_method_for(unit_name, _options = {}) ⇒ void
Defines a Numeric helper method that builds an Attribute of this unit.
-
.add_numeric_methods? ⇒ Boolean
Whether numeric helper methods should be generated.
-
.add_to_system(unit_sym) ⇒ Symbol?
Associates a unit with the currently active measurement system.
-
.conversion_rate(from, to) ⇒ Float?
Returns the conversion factor from one unit to another.
-
.convert(amount, from, to) ⇒ Numeric
Converts an amount from one unit to another.
-
.non_primitives ⇒ Array<Symbol>
The non-primitive (derived) units for this attribute class.
-
.numeric_methods(*args) ⇒ Array<Symbol>
Enables numeric helper methods for the given units.
- .one(sym, options = {}) ⇒ void
- .primitive(sym, options = {}) ⇒ void
-
.primitives ⇒ Array<Symbol>
The base (primitive) units for this attribute class.
-
.register_unit(multiple_unit, other_unit, multiple) ⇒ void
Records a conversion relationship between a derived unit and its base unit.
-
.system(system_name) { ... } ⇒ Object
Sets the current measurement system while registering units.
-
.systems ⇒ Array<Symbol>
The registered measurement systems.
-
.units(system = nil) ⇒ Array<Symbol>
Returns all registered units, optionally filtered by measurement system.
Instance Method Summary collapse
-
#<=>(other) ⇒ Integer
-1, 0, or 1 based on comparison.
-
#==(other) ⇒ Boolean
Whether the values are equal after unit conversion.
-
#eql?(other) ⇒ Boolean
Whether class, amount, and unit are identical.
-
#initialize(amount, unit) ⇒ Attribute
constructor
A new instance of Attribute.
-
#inspect ⇒ String
Developer-facing inspection string.
-
#method_missing(meth, *args) ⇒ Attribute, Object
Supports arithmetic against other Attributes and delegates to Numeric otherwise.
-
#system ⇒ Symbol?
The measurement system this unit belongs to.
-
#to_s ⇒ String
Human-readable amount and unit.
Constructor Details
#initialize(amount, unit) ⇒ Attribute
Returns a new instance of Attribute.
227 228 229 230 231 232 |
# File 'app/services/shipping/package.rb', line 227 def initialize(amount, unit) raise ArgumentError, "amount must be a Numeric" unless amount.is_a?(Numeric) @amount = amount @unit = unit.to_sym end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(meth, *args) ⇒ Attribute, Object
Supports arithmetic against other Attributes and delegates to Numeric otherwise.
278 279 280 281 282 283 284 285 |
# File 'app/services/shipping/package.rb', line 278 def method_missing(meth, *args) if args.size == 1 && instance_of?((other = args.first).class) other_amount_in_self_units = self.class.convert(other.amount, other.unit, unit) self.class.new(amount.send(meth, other_amount_in_self_units), unit) else amount.send(meth, *args) end end |
Instance Attribute Details
#amount ⇒ Object (readonly)
Returns the value of attribute amount.
222 223 224 |
# File 'app/services/shipping/package.rb', line 222 def amount @amount end |
#unit ⇒ Object (readonly)
Returns the value of attribute unit.
222 223 224 |
# File 'app/services/shipping/package.rb', line 222 def unit @unit end |
Class Method Details
.add_conversion(multiple_unit, other_unit, multiple) ⇒ void
This method returns an undefined value.
Stores a bidirectional conversion factor between two units.
448 449 450 451 452 453 |
# File 'app/services/shipping/package.rb', line 448 def self.add_conversion(multiple_unit, other_unit, multiple) conversions[multiple_unit] ||= {} conversions[multiple_unit][other_unit] = multiple conversions[other_unit] ||= {} conversions[other_unit][multiple_unit] = (1.0 / multiple) end |
.add_conversion_method_for(sym, _options = {}) ⇒ void
This method returns an undefined value.
Defines instance conversion methods for the given unit.
486 487 488 489 490 491 492 493 494 495 496 |
# File 'app/services/shipping/package.rb', line 486 def self.add_conversion_method_for(sym, = {}) unit_name = sym.to_s class_eval do define_method("to_#{unit_name}") do return self if unit_name == unit.to_s self.class.new(self.class.convert(amount, unit, unit_name), unit_name) end alias_method("in_#{unit_name}", "to_#{unit_name}") end end |
.add_methods_for(sym, options = {}) ⇒ void
This method returns an undefined value.
471 472 473 474 475 476 477 478 479 |
# File 'app/services/shipping/package.rb', line 471 def self.add_methods_for(sym, = {}) add_conversion_method_for(sym, ) add_numeric_method = if .key?(:add_numeric_methods) [:add_numeric_methods] else add_numeric_methods end add_numeric_method_for(sym.to_s, ) if add_numeric_method end |
.add_numeric_method_for(unit_name, _options = {}) ⇒ void
This method returns an undefined value.
Defines a Numeric helper method that builds an Attribute of this unit.
504 505 506 507 508 509 510 511 512 513 514 |
# File 'app/services/shipping/package.rb', line 504 def self.add_numeric_method_for(unit_name, = {}) unit_name = unit_name.to_sym raise ArgumentError, "#{unit_name.inspect} is not a unit in #{name}" unless units.include?(unit_name) klass = self Numeric.class_eval do define_method(unit_name) do klass.new(self, unit_name.to_sym) end end end |
.add_numeric_methods? ⇒ Boolean
Returns whether numeric helper methods should be generated.
347 348 349 |
# File 'app/services/shipping/package.rb', line 347 def self.add_numeric_methods? add_numeric_methods end |
.add_to_system(unit_sym) ⇒ Symbol?
Associates a unit with the currently active measurement system.
397 398 399 400 401 402 403 404 405 |
# File 'app/services/shipping/package.rb', line 397 def self.add_to_system(unit_sym) return unless current_system units_to_systems[unit_sym] ||= begin sys_ary = systems_to_units[current_system] ||= [] sys_ary << unit_sym current_system end end |
.conversion_rate(from, to) ⇒ Float?
Returns the conversion factor from one unit to another.
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'app/services/shipping/package.rb', line 293 def self.conversion_rate(from, to) return nil unless conversions[from] && conversions[to] # rubocop:disable Lint/NoReturnInBeginEndBlocks conversions[from][to] ||= (1.0 / conversions[to][from] if conversions[to][from]) || begin shared_conversions = conversions[from].keys & conversions[to].keys if shared_conversions.any? primitive = shared_conversions.first conversions[from][primitive] * (1.0 / conversions[to][primitive]) else conversions[from].each do |conversion_unit, multiple| return multiple * conversion_rate(conversion) * (1.0 / conversions[to][conversion_unit]) if conversions[to].include?(conversion_unit) end from_primitive = (conversions[from].keys & primitives).first to_primitive = (conversions[to].keys & primitives).first if (from_primitive_to_primitive_multiple = conversion_rate(from_primitive, to_primitive)) return conversions[from][from_primitive] * from_primitive_to_primitive_multiple * (1.0 / conversions[to][to_primitive]) end raise StandardError, "No conversion path from #{from} to #{to}" end end # rubocop:enable Lint/NoReturnInBeginEndBlocks end |
.convert(amount, from, to) ⇒ Numeric
Converts an amount from one unit to another.
461 462 463 464 465 |
# File 'app/services/shipping/package.rb', line 461 def self.convert(amount, from, to) from = from.to_sym to = to.to_sym amount * conversion_rate(from, to) end |
.non_primitives ⇒ Array<Symbol>
Returns the non-primitive (derived) units for this attribute class.
337 338 339 |
# File 'app/services/shipping/package.rb', line 337 def self.non_primitives conversions.keys end |
.numeric_methods(*args) ⇒ Array<Symbol>
Enables numeric helper methods for the given units.
355 356 357 358 359 |
# File 'app/services/shipping/package.rb', line 355 def self.numeric_methods(*args) args.each do |arg| add_numeric_method_for(arg.to_sym) end end |
.one(sym, options = {}) ⇒ void
This method returns an undefined value.
412 413 414 415 416 417 |
# File 'app/services/shipping/package.rb', line 412 def self.one(sym, = {}) unit_sym = ([:plural] || sym.to_s.pluralize).to_sym add_to_system(unit_sym) register_unit(unit_sym, [:is].unit, [:is].amount) add_methods_for(unit_sym, ) end |
.primitive(sym, options = {}) ⇒ void
This method returns an undefined value.
386 387 388 389 390 391 |
# File 'app/services/shipping/package.rb', line 386 def self.primitive(sym, = {}) unit_sym = ([:plural] || sym.to_s.pluralize).to_sym primitives << unit_sym add_to_system(unit_sym) add_methods_for(unit_sym, ) end |
.primitives ⇒ Array<Symbol>
Returns the base (primitive) units for this attribute class.
332 333 334 |
# File 'app/services/shipping/package.rb', line 332 def self.primitives read_inheritable_attribute(:primitives).dup end |
.register_unit(multiple_unit, other_unit, multiple) ⇒ void
This method returns an undefined value.
Records a conversion relationship between a derived unit and its base unit.
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 |
# File 'app/services/shipping/package.rb', line 425 def self.register_unit(multiple_unit, other_unit, multiple) multiple_unit = multiple_unit.to_sym other_unit = other_unit.to_sym conversions[multiple_unit] ||= {} conversions[other_unit] ||= {} if primitives.include?(multiple_unit) || primitives.include?(other_unit) add_conversion(multiple_unit, other_unit, multiple) else [multiple_unit, other_unit].each do |this_unit| conversions[this_unit].each do |this_other_unit, this_multiple| add_conversion(multiple_unit, this_other_unit, multiple * this_multiple) if primitives.include?(this_other_unit) end end end end |
.system(system_name) { ... } ⇒ Object
Sets the current measurement system while registering units.
375 376 377 378 379 380 |
# File 'app/services/shipping/package.rb', line 375 def self.system(system_name, &) old_system = current_system self.current_system = system_name.to_sym yield self.current_system = old_system end |
.systems ⇒ Array<Symbol>
Returns the registered measurement systems.
342 343 344 |
# File 'app/services/shipping/package.rb', line 342 def self.systems systems_to_units.keys end |
.units(system = nil) ⇒ Array<Symbol>
Returns all registered units, optionally filtered by measurement system.
323 324 325 326 327 328 329 |
# File 'app/services/shipping/package.rb', line 323 def self.units(system = nil) if system systems_to_units[system.to_sym].dup else read_inheritable_attribute(:primitives) | conversions.keys end end |
Instance Method Details
#<=>(other) ⇒ Integer
Returns -1, 0, or 1 based on comparison.
260 261 262 263 264 265 266 |
# File 'app/services/shipping/package.rb', line 260 def <=>(other) if instance_of?(other.class) self.class.convert(amount, unit, other.unit) <=> other.amount else amount <=> other end end |
#==(other) ⇒ Boolean
Returns whether the values are equal after unit conversion.
246 247 248 249 250 |
# File 'app/services/shipping/package.rb', line 246 def ==(other) (BigDecimal(amount&.to_s) == BigDecimal(other&.amount&.to_s) && unit == other&.unit) || BigDecimal(self.class&.convert(amount, unit, other&.unit)&.to_s) == BigDecimal(other&.amount&.to_s) rescue NoMethodError amount == other end |
#eql?(other) ⇒ Boolean
Returns whether class, amount, and unit are identical.
254 255 256 |
# File 'app/services/shipping/package.rb', line 254 def eql?(other) self.class == other.class && BigDecimal(amount.to_s) == BigDecimal(other.amount.to_s) && unit == other.unit end |
#inspect ⇒ String
Returns developer-facing inspection string.
240 241 242 |
# File 'app/services/shipping/package.rb', line 240 def inspect "#<#{self.class.name}: #{amount} #{unit}>" end |
#system ⇒ Symbol?
Returns the measurement system this unit belongs to.
269 270 271 |
# File 'app/services/shipping/package.rb', line 269 def system self.class.units_to_systems[unit] end |
#to_s ⇒ String
Returns human-readable amount and unit.
235 236 237 |
# File 'app/services/shipping/package.rb', line 235 def to_s "#{amount} #{unit}" end |