commerce_units 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/commerce_units.gemspec +34 -0
- data/lib/commerce_units/converter.rb +56 -0
- data/lib/commerce_units/dimension.rb +102 -0
- data/lib/commerce_units/simplifier.rb +33 -0
- data/lib/commerce_units/terms_reducer.rb +63 -0
- data/lib/commerce_units/unit.rb +98 -0
- data/lib/commerce_units/unit_lexer.rb +67 -0
- data/lib/commerce_units/unit_parser.rb +68 -0
- data/lib/commerce_units/value.rb +62 -0
- data/lib/commerce_units/version.rb +3 -0
- data/lib/commerce_units.rb +27 -0
- data/lib/generators/commerce_units/USAGE +7 -0
- data/lib/generators/commerce_units/install_generator.rb +38 -0
- data/lib/generators/commerce_units/templates/migrations/create_commerce_units_dimensions.rb.erb +11 -0
- data/spec/commerce_units/converter_spec.rb +37 -0
- data/spec/commerce_units/dimension_spec.rb +40 -0
- data/spec/commerce_units/simplifier_spec.rb +22 -0
- data/spec/commerce_units/terms_reducer_spec.rb +18 -0
- data/spec/commerce_units/unit_lexer_spec.rb +15 -0
- data/spec/commerce_units/unit_parser_spec.rb +15 -0
- data/spec/commerce_units/unit_spec.rb +15 -0
- data/spec/commerce_units/value_spec.rb +71 -0
- data/spec/database.yml +3 -0
- data/spec/debug.log +2753 -0
- data/spec/factories/base_factory.rb +30 -0
- data/spec/factories/dimension_factory.rb +41 -0
- data/spec/factories/length_factory.rb +39 -0
- data/spec/factories/time_factory.rb +31 -0
- data/spec/fixtures/migration.rb +11 -0
- data/spec/spec_helper.rb +17 -0
- metadata +224 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1abedfde0d6bd48016d5daaae8b936b1f6cdb3e5
|
4
|
+
data.tar.gz: 1a9d0c12f33623e6b2065cb2c7f1003a1dc43e2d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac8f6d6df3a1cc8c33fc79d28be9af62bb0d816c543d825aa65a2010e5dcd9ebc622785c55f56826ba7436c8ad44d6d1491549608b4ec8434361bc480aa3cc3a
|
7
|
+
data.tar.gz: 379345b2ea3e703311bcf26c24b8608603a7b9834537bc40f618763375207e4ad21e75b105248b0a928fba03e2bf87f87e8a64b93c54fd4ae8424bcbec54b809
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Thomas Chen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# CommerceUnits
|
2
|
+
|
3
|
+
Another units library for Ruby which helps with maintaining consist mathematics. But unlike scientific (SI) units, where we're all agreed to things like 1000g = 1kg, 1 hour = 60 minutes, and whatnot, commerce_units makes no assumptions on what kinds of units there.
|
4
|
+
|
5
|
+
Instead, commerical units allows you to define your own conversion rates between different units and operations such as multiply and divide will automatically handle conversions and reductions while addition and subtraction will throw errors if inappropriately dimensioned values are added together.
|
6
|
+
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'commerce_units'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install commerce_units
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Step 1: generate the migration to create the dimensions
|
25
|
+
```shell
|
26
|
+
rails generate commerce_units
|
27
|
+
```
|
28
|
+
What?! This is unit library, yet you demand a database migration? What sort of stupid shit...
|
29
|
+
|
30
|
+
Well, the justification is that this is a commerce units library, and, in business, I have no idea what sort of units you might be using to quantify your transactions. In my case, I built this library originally to use for money per weight, and the dimension of money has all sorts of units (dollars, cents, rupies, RMB, yen, pound-sterling, etc.) which even change with time.
|
31
|
+
|
32
|
+
If you think your business is measured in dollars per boxes, then it's up to you to declare dollars with a money dimension and boxes in some other root dimension.
|
33
|
+
|
34
|
+
### Step 2: seed the CommerceUnits::Dimension record with the units you'll need
|
35
|
+
Declare your units, here's an example
|
36
|
+
```ruby
|
37
|
+
CommerceUnits::Dimension.create! root_dimension: :money,
|
38
|
+
unit_name: "dollar",
|
39
|
+
multiply_constant: 1.0,
|
40
|
+
unitary_role: :primary
|
41
|
+
|
42
|
+
CommerceUnits::Dimension.create! root_dimension: :money,
|
43
|
+
unit_name: "cent",
|
44
|
+
multiply_constant: 100.0 # 100 cents go into 1 us dollar
|
45
|
+
|
46
|
+
CommerceUnits::Dimension.create! root_dimension: :money,
|
47
|
+
unit_name: "RMB",
|
48
|
+
multiply_constant: 6.654 # 6.654 RMB go into 1 us dollar
|
49
|
+
|
50
|
+
CommerceUnits::Dimension.create! root_dimension: :money,
|
51
|
+
unit_name: "euro",
|
52
|
+
multiply_constant: 12000 # 12000 euros go into 1 us dollar because Yuropoor is poor
|
53
|
+
|
54
|
+
CommerceUnits::Dimension.create! root_dimension: :mass,
|
55
|
+
unit_name: 'pound',
|
56
|
+
multiply_constant: 1.0,
|
57
|
+
unitary_role: :primary
|
58
|
+
|
59
|
+
CommerceUnits::Dimension.create! root_dimension: :mass,
|
60
|
+
unit_name: 'ton',
|
61
|
+
multiply_constant: 2000 # 2000 pounds go into 1 ton
|
62
|
+
|
63
|
+
CommerceUnits::Dimension.create! root_dimension: :mass,
|
64
|
+
unit_name: 'landwhale',
|
65
|
+
multiply_constant: 354 # 354 pounds go into 1 landwhale
|
66
|
+
```
|
67
|
+
|
68
|
+
### Step 3: Use
|
69
|
+
Here's an example
|
70
|
+
```ruby
|
71
|
+
dollar_per_pound = CommerceUnit::Value.from_params number: 1234, units: "dollar / pound"
|
72
|
+
dollar_per_pound.to_s # 1234 dollar / pound
|
73
|
+
|
74
|
+
euro_per_ton = CommerceUnit::Value.from_params number: 34, units: "euro / ton"
|
75
|
+
dollar_per_pound + euro_per_ton # 1234.00000... dollar / pound
|
76
|
+
|
77
|
+
dollar_per_landwhale_squared = CommerceUnit::Value.from_params number: 1, units: "dollar / landwhale / landwhale"
|
78
|
+
euro_per_ton + dollar_per_landwhale_squared # throws unit mismatch error
|
79
|
+
|
80
|
+
(euro_per_ton / dollar_per_pound ).unitless? # true
|
81
|
+
|
82
|
+
```
|
83
|
+
## Assumptions
|
84
|
+
If "mango" and "chair" are both declared as commerce units of the same dimension, then any amount of mangos can be converted to chairs by a constant multiplication. This necessarily means 0 mangos == 0 chairs
|
85
|
+
|
86
|
+
If you specify 0 as the multiply_constant, you'll eat a face full of DivideByZero errors.
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it ( http://github.com/<my-github-username>/commerce_units/fork )
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'commerce_units/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "commerce_units"
|
8
|
+
spec.version = CommerceUnits::VERSION
|
9
|
+
spec.authors = ["Thomas Chen"]
|
10
|
+
spec.email = ["foxnewsnetwork@gmail.com"]
|
11
|
+
spec.summary = %q{Another ruby units library for doing dimensional math, this one to be used with rails, preferrably commerce-related apps}
|
12
|
+
spec.description = %q{Another ruby units library for doing dimensional math, this one to be used with rails, preferrably commerce-related apps}
|
13
|
+
spec.homepage = "http://github.com/foxnewsnetwork/commerce_units"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">= 1.9.2"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", ">= 2.14"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
spec.add_development_dependency "ffaker"
|
28
|
+
|
29
|
+
spec.add_dependency('activemodel', '>= 3.0.0')
|
30
|
+
spec.add_dependency('activerecord', '>= 3.0.0')
|
31
|
+
spec.add_dependency('activesupport', '>= 3.0.0')
|
32
|
+
spec.add_dependency "functional_support", '>= 0.0.6'
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class CommerceUnits::Converter
|
2
|
+
class << self
|
3
|
+
def convert(value: value, unit: unit)
|
4
|
+
new.tap do |c|
|
5
|
+
c.target_unit = unit
|
6
|
+
c.origin_value = value
|
7
|
+
end.coerce
|
8
|
+
end
|
9
|
+
end
|
10
|
+
class MismatchDimension < StandardError; end
|
11
|
+
class WrongType < StandardError; end
|
12
|
+
attr_reader :target_unit, :origin_value
|
13
|
+
|
14
|
+
def coerce
|
15
|
+
return origin_value if target_unit == origin_unit
|
16
|
+
raise MismatchDimension, "Unable to convert #{origin_unit} to #{target_unit}" if _mismatch_dimension?
|
17
|
+
_base_to_target_transform _origin_to_base_transform origin_value
|
18
|
+
end
|
19
|
+
|
20
|
+
def origin_unit
|
21
|
+
origin_value.unit
|
22
|
+
end
|
23
|
+
|
24
|
+
def target_unit=(unit)
|
25
|
+
raise WrongType, "#{unit} should be an instance of CommerceUnits::Unit" unless unit.is_a? CommerceUnits::Unit
|
26
|
+
@target_unit = unit
|
27
|
+
end
|
28
|
+
|
29
|
+
def origin_value=(value)
|
30
|
+
raise WrongType, "#{value} should be an instance of CommerceUnits::Value" unless value.is_a? CommerceUnits::Value
|
31
|
+
@origin_value = value
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _origin_to_base_transform(value)
|
37
|
+
_conversion_constants(origin_unit).inject(value) { |v, c| v * c }
|
38
|
+
end
|
39
|
+
|
40
|
+
def _conversion_constants(unit)
|
41
|
+
CommerceUnits.dimensional_database.from_array_of_unit_names!(unit.numerator).map(&:to_converter_value) +
|
42
|
+
CommerceUnits.dimensional_database.from_array_of_unit_names!(unit.denominator).map(&:to_converter_value).map(&:flip)
|
43
|
+
end
|
44
|
+
|
45
|
+
def _base_to_target_transform(value)
|
46
|
+
_conversion_constants(target_unit).inject(value) { |v,c| v * c }
|
47
|
+
end
|
48
|
+
|
49
|
+
def _mismatch_dimension?
|
50
|
+
not _matching_dimension?
|
51
|
+
end
|
52
|
+
|
53
|
+
def _matching_dimension?
|
54
|
+
target_unit.to_root_dimension == origin_unit.to_root_dimension
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# == Schema Information
|
2
|
+
#
|
3
|
+
# Table name: commerce_units_dimensions
|
4
|
+
#
|
5
|
+
# id :integer not null, primary key
|
6
|
+
# root_dimension :string(255) not null
|
7
|
+
# unit_name :string(255) not null
|
8
|
+
# multiply_constant :decimal(15, 5) default(1.0), not null
|
9
|
+
# unitary_role :string(255) default("tertiary"), not null
|
10
|
+
# created_at :datetime
|
11
|
+
# updated_at :datetime
|
12
|
+
#
|
13
|
+
|
14
|
+
class CommerceUnits::Dimension < ActiveRecord::Base
|
15
|
+
class NoPrimaryUnit < StandardError; end
|
16
|
+
KnownRoots = [:money, :mass]
|
17
|
+
|
18
|
+
scope :primary_roles,
|
19
|
+
-> { where "#{self.table_name}.unitary_role = ?", :primary }
|
20
|
+
|
21
|
+
scope :by_roots,
|
22
|
+
-> (root_name) { where "#{self.table_name}.root_dimension = ?", root_name }
|
23
|
+
|
24
|
+
scope :primary_unit_by_roots,
|
25
|
+
-> (root_name) { primary_roles.by_roots(root_name) }
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def from_array_of_unit_names!(names)
|
29
|
+
result = where unit_name: names
|
30
|
+
unless result.count == names.count
|
31
|
+
unfound_names = names.map(&:to_s) - result.map(&:unit_name)
|
32
|
+
raise ActiveRecord::RecordNotFound, "I don't know the following units: #{unfound_names} out of #{names}"
|
33
|
+
end
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_and_consider_making_primary!(param_hash)
|
38
|
+
dimension = create! param_hash
|
39
|
+
dimension.make_primary! if primary_unit_by_roots(dimension.root_dimension).blank?
|
40
|
+
dimension
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_or_create_and_consider_making_primary!(param_hash)
|
44
|
+
where(param_hash).first || create_and_consider_making_primary!(param_hash)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_converter_unit
|
49
|
+
CommerceUnits::Unit.new.tap do |u|
|
50
|
+
u.numerator = [_primary_root_unit_name]
|
51
|
+
u.denominator = [unit_name]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_unit
|
56
|
+
CommerceUnits::Unit.new.tap do |u|
|
57
|
+
u.numerator = [unit_name]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_converter_value
|
62
|
+
CommerceUnits::Value.new.tap do |v|
|
63
|
+
v.number = multiply_constant
|
64
|
+
v.unit = to_converter_unit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_value
|
69
|
+
CommerceUnits::Value.new.tap do |v|
|
70
|
+
v.number = multiply_constant
|
71
|
+
v.unit = to_unit
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def make_primary!
|
76
|
+
_shift_primary! if self.class.primary_unit_by_roots(root_dimension).present?
|
77
|
+
_make_primary!
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def _make_primary!
|
83
|
+
update multiply_constant: 1.0,
|
84
|
+
unitary_role: :primary
|
85
|
+
end
|
86
|
+
def _shift_primary!
|
87
|
+
self.class.by_roots(root_dimension).reject do |dim|
|
88
|
+
dim.id == self.id
|
89
|
+
end.each do |dim|
|
90
|
+
dim.update multiply_constant: dim.multiply_constant / multiply_constant,
|
91
|
+
unitary_role: :secondary
|
92
|
+
end
|
93
|
+
end
|
94
|
+
def _primary_unit
|
95
|
+
self.class.primary_unit_by_roots(root_dimension).first
|
96
|
+
end
|
97
|
+
def _primary_root_unit_name
|
98
|
+
d = _primary_unit
|
99
|
+
raise NoPrimaryUnit, "#{root_dimension} does not have a primary unit" if d.blank?
|
100
|
+
d.unit_name
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class CommerceUnits::Simplifier
|
2
|
+
class NotComparable < StandardError; end
|
3
|
+
def initialize(numerator: [], denominator: [])
|
4
|
+
@original_top = numerator.sort
|
5
|
+
@original_bot = denominator.sort
|
6
|
+
end
|
7
|
+
|
8
|
+
def numerator
|
9
|
+
@numerator ||= _reduced_numerator_from top: @original_top, bot: @original_bot
|
10
|
+
end
|
11
|
+
|
12
|
+
def denominator
|
13
|
+
@denominator ||= _reduced_denominator_from top: @original_top, bot: @original_bot
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def _reduced_numerator_from(top: [], bot: [])
|
18
|
+
return top if top.blank? or bot.blank?
|
19
|
+
return _reduced_numerator_from(top: top.tail, bot: bot.tail) if top.first == bot.first
|
20
|
+
return _reduced_numerator_from(top: top, bot: bot.tail) if top.first > bot.first
|
21
|
+
return [top.first] + _reduced_numerator_from(top: top.tail, bot: bot) if top.first < bot.first
|
22
|
+
raise NotComparable, "Failed to compare #{top.first} with #{bot.first}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def _reduced_denominator_from(top: [], bot: [])
|
26
|
+
return bot if top.blank? or bot.blank?
|
27
|
+
return _reduced_denominator_from(top: top.tail, bot: bot.tail) if top.first == bot.first
|
28
|
+
return [bot.first] + _reduced_denominator_from(top: top, bot: bot.tail) if bot.first < top.first
|
29
|
+
return _reduced_denominator_from(top: top.tail, bot: bot) if bot.first > top.first
|
30
|
+
raise NotComparable, "Failed to compare #{top.first} with #{bot.first}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class CommerceUnits::TermsReducer
|
2
|
+
attr_accessor :value
|
3
|
+
delegate :number, :unit, to: :value
|
4
|
+
def initialize(value)
|
5
|
+
@value = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def reduce_to_simplest_terms
|
9
|
+
_reducing_terms.reduce(value, &:*)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def _reducing_terms
|
14
|
+
_units_by_dimensions.flat_map do |root_dimension, numerators_and_denominators|
|
15
|
+
numerators_and_denominators[:numerators].zip numerators_and_denominators[:denominators]
|
16
|
+
end.reject do |top_and_bottom|
|
17
|
+
top_and_bottom.first.blank? || top_and_bottom.last.blank?
|
18
|
+
end.map do |top_and_bottom|
|
19
|
+
_value_from_ratio *top_and_bottom
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def _units_by_dimensions
|
24
|
+
_special_merge _numerator_by_dimensions, _denominator_by_dimensions
|
25
|
+
end
|
26
|
+
|
27
|
+
def _special_merge(top_group, bot_group)
|
28
|
+
output = {}
|
29
|
+
top_group.each do |root_dimension, unit_names|
|
30
|
+
output[root_dimension] ||= {}
|
31
|
+
output[root_dimension][:numerators] = unit_names
|
32
|
+
end
|
33
|
+
bot_group.each do |root_dimension, unit_names|
|
34
|
+
output[root_dimension] ||= {}
|
35
|
+
output[root_dimension][:denominators] = unit_names
|
36
|
+
end
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
def _numerator_by_dimensions
|
41
|
+
unit.numerator.group_by { |unit_name| _get_root_dim unit_name }
|
42
|
+
end
|
43
|
+
|
44
|
+
def _denominator_by_dimensions
|
45
|
+
unit.denominator.group_by { |unit_name| _get_root_dim unit_name }
|
46
|
+
end
|
47
|
+
|
48
|
+
def _value_from_ratio(top, bottom)
|
49
|
+
_get_conversion_value(top) / _get_conversion_value(bottom)
|
50
|
+
end
|
51
|
+
|
52
|
+
def _get_conversion_value(unit_name)
|
53
|
+
_get_dim(unit_name).to_converter_value
|
54
|
+
end
|
55
|
+
|
56
|
+
def _get_dim(unit_name)
|
57
|
+
CommerceUnits.dimensional_database.find_by_unit_name! unit_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def _get_root_dim(unit_name)
|
61
|
+
_get_dim(unit_name).root_dimension
|
62
|
+
end
|
63
|
+
end
|