lib/quantify/unit/base_unit.rb in quantify-1.0.4 vs lib/quantify/unit/base_unit.rb in quantify-1.0.5

- old
+ new

@@ -9,13 +9,12 @@ # by SI and NonSI unit classes. # Create a new instance of self (i.e. Base or an inherited class) and load # into the system of known units. See initialize for details of options # - def self.load(options) - unit = self.new(options) - unit.load + def self.load(options=nil,&block) + self.new(options,&block).load end def self.construct_and_load(unit,&block) self.construct(unit, &block).load end @@ -48,12 +47,12 @@ new_unit = self.new unit.to_hash yield new_unit if block_given? return new_unit end - # Syntactic sugar for defining the known units, enabling the required - # associated units to be loaded at runtime, e.g. + # Syntactic sugar for defining the units known to the system, enabling the + # required associated units to be loaded at runtime, e.g. # # Unit::[Base|SI|NonSI].configure do |config| # # load :name => :metre, :physical_quantity => :length # load :name => 'hectare', :physical_quantity => :area, :factor => 10000 @@ -63,13 +62,13 @@ # def self.configure &block class_eval &block if block end - attr_accessor :name, :symbol, :label - attr_accessor :dimensions, :factor - attr_accessor :acts_as_alternative_unit, :acts_as_equivalent_unit + attr_accessor :name, :symbol, :label, :factor + attr_reader :dimensions + attr_reader :acts_as_alternative_unit, :acts_as_equivalent_unit # Create a new Unit::Base instance. # # Valid options are: :name => The unit name, e.g. :kilometre # @@ -99,56 +98,73 @@ # # The physical quantity option is used to locate the corresponding dimensional # representation in the Dimensions class. This dimensions attribute is to # provide much of the unit functionality # - def initialize(options=nil) + def initialize(options=nil) + @acts_as_alternative_unit = true + @acts_as_equivalent_unit = false + @factor = 1.0 + @symbol = nil + @label = nil if options.is_a? Hash + self.dimensions = options[:dimensions] || options[:physical_quantity] @name = options[:name].standardize.singularize.downcase - options[:dimensions] = options[:dimensions] || options[:physical_quantity] - if options[:dimensions].is_a? Dimensions - @dimensions = options[:dimensions] - elsif options[:dimensions].is_a? String or options[:dimensions].is_a? Symbol - @dimensions = Dimensions.for options[:dimensions] - else - raise Exceptions::InvalidArgumentError, "Unknown physical_quantity specified" - end - @factor = options[:factor].nil? ? 1.0 : options[:factor].to_f - @symbol = options[:symbol].nil? ? nil : options[:symbol].standardize - @label = options[:label].nil? ? nil : options[:label].to_s - @acts_as_alternative_unit = true - @acts_as_equivalent_unit = false + @factor = options[:factor].to_f if options[:factor] + @symbol = options[:symbol].standardize if options[:symbol] + @label = options[:label].to_s if options[:label] end yield self if block_given? valid? end + def dimensions=(dimensions) + if dimensions.is_a? Dimensions + @dimensions = dimensions + elsif dimensions.is_a? String or dimensions.is_a? Symbol + @dimensions = Dimensions.for dimensions + else + raise Exceptions::InvalidArgumentError, "Unknown physical_quantity specified" + end + end + alias :physical_quantity= :dimensions= + # Permits a block to be used, operating on self. This is useful for modifying # the attributes of an already instantiated unit, especially when defining # units on the basis of operation on existing units for adding specific # (rather than derived) names or symbols, e.g. # - # (Unit.pound_force/(Unit.in**2)).operate do |unit| + # (Unit.pound_force/(Unit.in**2)).configure do |unit| # unit.symbol = 'psi' # unit.label = 'psi' # unit.name = 'pound per square inch' # end # - def operate + def configure yield self if block_given? return self if valid? end + # Similar to #configure but makes the new unit configuration the canonical + # unit for self.label + # + def configure_as_canonical &block + unload if loaded? + configure &block if block_given? + make_canonical + end + # Load an initialized Unit into the system of known units. # - # If a block is given, the unit can be operated on prior to loading, in a - # similar to way to the #operate method. + # If a block is given, the unit can be configured prior to loading, in a + # similar to way to the #configure method. # def load yield self if block_given? raise Exceptions::InvalidArgumentError, "A unit with the same label: #{self.name}) already exists" if loaded? Quantify::Unit.units << self if valid? + return self end # Remove from system of known units. def unload Unit.unload(self.label) @@ -157,15 +173,23 @@ # check if an object with the same label already exists def loaded? Unit.units.any? { |unit| self.has_same_identity_as? unit } end + # Make self the canonical representation of the unit defined by self#label def make_canonical - unload + unload if loaded? load end + # Set the canonical unit label - the unique unit identifier - to a new value + def canonical_label=(new_label) + unload if loaded? + self.label = new_label + load + end + def acts_as_alternative_unit=(value) @acts_as_alternative_unit = (value == (true||false) ? value : false) make_canonical end @@ -201,15 +225,25 @@ def pluralized_name self.name.pluralize end - # Determine if the unit represents one of the base quantities + # Determine if the unit represents one of the base quantities, length, + # mass, time, temperature, etc. + # def is_base_unit? Dimensions::BASE_QUANTITIES.map(&:standardize).include? self.measures end + # Determine if the unit is THE canonical SI unit for a base quantity (length, + # mass, time, etc.). This method ignores prefixed versions of SI base units, + # returning true only for metre, kilogram, second, Kelvin, etc. + # + def is_base_quantity_si_unit? + is_si_unit? and is_base_unit? and is_benchmark_unit? + end + # Determine is the unit is a derived unit - that is, a unit made up of more # than one of the base quantities # def is_derived_unit? not is_base_unit? @@ -223,11 +257,12 @@ end # Determine if the unit is one of the units against which all other units # of the same physical quantity are defined. These units are almost entirely # equivalent to the non-prefixed, SI units, but the one exception is the - # kilogram, making this method necessary. + # kilogram, which is an oddity in being THE canonical SI unit for mass, yet + # containing a prefix. This oddity makes this method useful/necessary. # def is_benchmark_unit? self.factor == 1.0 end @@ -275,11 +310,11 @@ def is_same_as?(other) [:dimensions,:factor,:scaling].all? do |attr| self.send(attr) == other.send(attr) end end - + alias :== :is_same_as? # Check if unit has the identity as another, i.e. the same label. This is # used to determine if a unit with the same accessors already exists in # the module variable @@units @@ -373,10 +408,12 @@ options = [] self.instance_of?(Unit::Compound) ? options += self.base_units : options << self other.instance_of?(Unit::Compound) ? options += other.base_units : options << other Unit::Compound.new(*options) end + alias :times :multiply + alias :* :multiply # Divide one unit by another. This results in the generation of a compound # unit. # # In the event that the new unit represents a known unit, the non-compound @@ -391,10 +428,11 @@ else options << CompoundBaseUnit.new(other,-1) end Unit::Compound.new(*options) end + alias :/ :divide # Raise a unit to a power. This results in the generation of a compound # unit, e.g. m^3. # # In the event that the new unit represents a known unit, the non-compound @@ -410,19 +448,15 @@ new_unit = reciprocalize ((power.abs) - 1).times { new_unit /= original_unit } end return new_unit end + alias :** :pow # Return new unit representing the reciprocal of self, i.e. 1/self def reciprocalize Unit.unity / self end - - alias :times :multiply - alias :* :multiply - alias :/ :divide - alias :** :pow # Apply a prefix to self. Returns new unit according to the prefixed version # of self, complete with modified name, symbol, factor, etc.. # def with_prefix(name_or_symbol)