unit_measurements 5.1.1 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a5ba942c1c99f76efc0db51ce916d31d199646d5bd2a5ace71b34fadbadacb2
4
- data.tar.gz: 681c3ce17cebe41d715c78b25669afe6347d726405b4fcea74ffd8da3d5e9dc4
3
+ metadata.gz: 006027cb5f557c69f5ba083f6624cf9e424fcb00cc794b8f50912907538a3689
4
+ data.tar.gz: 4db6ec5c12a752191933c5aa6d99837c1e2dc5e8269e5d05339a6e9b631d3e7f
5
5
  SHA512:
6
- metadata.gz: 518cdb270f00447a0a7817e4db3bf8dbd83e6ed1ccdb82e58ef60579d774eff9f40544669b216ab42ee7133e23cf69f1ce149c7d051e8ffc3e147cc356361392
7
- data.tar.gz: 239e100a9b733cbe1792ffc54c812a5fce1abecc2e449bdd03ee6215b84e72ad98eaaa1abec9d9e389deee515fef04cdef022e7c8fe6ab765c8b436eb4f0b0b2
6
+ metadata.gz: fe13840ae30879e2068b4a0628e073259d8faab8efc615f0ff6da1b4fc7307b2a4285ebe44425fc4bedf26263e02a5f29e34df6ac792e3c16429f214262dbc31
7
+ data.tar.gz: d9f28aecb8b8dea511466a8e4f71288581c35407d841b20d65006a9bb8e3dad8d3ec53da4b2f83c0235d9a919a2dccf572371957c1010a8d449c7b11bdf1947f
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /cache/*.json
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## [5.3.0](https://github.com/shivam091/unit_measurements/compare/v5.2.0...v5.3.0) - 2023-10-24
2
+
3
+ ### What's new
4
+
5
+ - Added ability set globally configurable options for **`unit_measurements`**.
6
+
7
+ ### What's improved
8
+
9
+ - Code coverage improvements.
10
+
11
+ ----------
12
+
13
+ ## [5.2.0](https://github.com/shivam091/unit_measurements/compare/v5.1.1...v5.2.0) - 2023-10-22
14
+
15
+ ### What's new
16
+
17
+ - Added ability to set name of the cache file for the unit group.
18
+ - Added support for caching conversion factors between units of the unit group.
19
+
20
+ ----------
21
+
1
22
  ## [5.1.1](https://github.com/shivam091/unit_measurements/compare/v5.1.0...v5.1.1) - 2023-10-20
2
23
 
3
24
  ### What's updated
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- unit_measurements (5.1.1)
4
+ unit_measurements (5.3.0)
5
5
  activesupport (~> 7.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -22,13 +22,16 @@ to numerous errors.
22
22
 
23
23
  The `unit_measurements` gem is designed to simplify the handling of units for scientific calculations.
24
24
 
25
- ## Features
26
-
27
- 1. Provides easy conversion between units.
28
- 2. Lightweight and easily extensible to include other units and conversions.
29
- 3. Built in support for various [unit groups](https://github.com/shivam091/unit_measurements/blob/main/units.md).
30
- 4. Well organized and very descriptive documentation published [here](https://shivam091.github.io/unit_measurements).
31
- 5. Parses strings representing complex, fractional, mixed fractional, scientific numbers, and ratios.
25
+ ## Key Features
26
+ 1. **Simplified Measurement Conversion:** Easily convert measurements between compatible units, reducing the likelihood of errors in scientific calculations.
27
+ 2. **Extensible Unit Groups:** Effortlessly build own unit groups with specific units and conversions tailored to your needs.
28
+ 3. **Built-in Unit Groups:** Comes bundled with a wide range of standard [unit groups](https://github.com/shivam091/unit_measurements/blob/main/units.md),
29
+ covering various units.
30
+ 4. **String Parsing Capabilities:** Effortlessly parse strings representing complex, fractional, mixed fractional, scientific numbers, and ratios directly
31
+ saving you the hassle of manually extracting and converting them.
32
+ 5. **Comprehensive Documentation:** Well-organized and descriptive [documentation](https://shivam091.github.io/unit_measurements) for quick reference and implementation guidance.
33
+ 6. **Configurable Options:** Fine-tune behavior with configurable options, including caching for enhanced performance.
34
+ 7. **Error Handling:** Robust error handling ensures stability and reliability during conversions.
32
35
 
33
36
  ## Disclaimer
34
37
 
@@ -56,6 +59,23 @@ Or otherwise simply install it yourself as:
56
59
 
57
60
  `$ gem install unit_measurements`
58
61
 
62
+ ## Configuration
63
+
64
+ `unit_measurements` is designed to work out of the box, but you can customize its behavior by placing
65
+ the configuration block in an initializer file before requiring the library files:
66
+
67
+ ```ruby
68
+ UnitMeasurements.configure do |config|
69
+ config.use_cache = false
70
+ end
71
+ ```
72
+
73
+ The current available configurable options are:
74
+
75
+ | Option | Default value | Description |
76
+ | ------ | ------------- | ----------- |
77
+ | **use_cache** | false | Set to `true` to enable caching during conversions. |
78
+
59
79
  ## Usage
60
80
 
61
81
  The **`UnitMeasurements::Measurement`** class is responsible for conversion of quantity to various compatible units
@@ -74,6 +94,8 @@ UnitMeasurements::Length.new(1, "km")
74
94
  This gem allows you to convert among units of same unit group. You can convert measurement to other unit using `#convert_to`
75
95
  (aliased as `#to`, `#in`, and `#as`) or `#convert_to!` (aliased as `#to!`, `#in!`, and `#as!`) methods.
76
96
 
97
+ These methods provide `use_cache` parameter which can be used to indicate whether the caching of conversion factors should happen.
98
+
77
99
  You can use `#convert_to` as:
78
100
 
79
101
  ```ruby
@@ -104,6 +126,8 @@ UnitMeasurements::Length.new(100, "m").convert_to("ft").convert_to!("in")
104
126
 
105
127
  **Parse string without having to split out the quantity and source unit:**
106
128
 
129
+ This method provides `use_cache` parameter which can be used to indicate whether the caching of conversion factors should happen.
130
+
107
131
  ```ruby
108
132
  UnitMeasurements::Length.parse("1 km")
109
133
  #=> 1.0 km
@@ -254,6 +278,12 @@ UnitMeasurements::Length.unit_or_alias?("metre")
254
278
  #=> true
255
279
  ```
256
280
 
281
+ **Clear cached data for the unit group:**
282
+
283
+ ```ruby
284
+ UnitMeasurements::Length.clear_cache
285
+ ```
286
+
257
287
  ### Comparisons
258
288
 
259
289
  You have ability to compare the measurements with the same or different units within the same unit group.
@@ -389,10 +419,9 @@ gem "unit_measurements", require: ["unit_measurements/base", "unit_measurements/
389
419
 
390
420
  ### Building new unit groups
391
421
 
392
- This library provides a simpler way to define your own unit groups. Use the
393
- `UnitMeasurements.build` method to define units within it. You can also group
394
- units by the unit system using the `system` method and set the primitive unit for
395
- each unit group using the `primitive` method.
422
+ This library provides a simpler way to define your own unit groups. Use the `UnitMeasurements.build` method to define
423
+ units within it. You can group units by the unit system using the `system` method and set the primitive unit for the
424
+ unit group using the `primitive` method. You can specify cache file name in unit group definition using the `cache` method.
396
425
 
397
426
  ```ruby
398
427
  UnitMeasurements::Time = UnitMeasurements.build do
@@ -413,6 +442,9 @@ UnitMeasurements::Time = UnitMeasurements.build do
413
442
  # You can also specify unit value as an array.
414
443
  unit "h", value: [60, "min"], aliases: ["day", "days"]
415
444
  end
445
+
446
+ # Sets the name of the cache file (optional).
447
+ cache "time_cache.json"
416
448
  end
417
449
  ```
418
450
 
@@ -7,6 +7,13 @@ require "unit_measurements/version"
7
7
 
8
8
  module UnitMeasurements
9
9
  class << self
10
+ # Allows setting an instance of +Configuration+ containing values of desired
11
+ # configurable options.
12
+ #
13
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
14
+ # @since 5.3.0
15
+ attr_writer :configuration
16
+
10
17
  # Creates a new unit group based on the provided +block+ of instructions.
11
18
  #
12
19
  # The +build+ method allows you to define and create a custom unit group with
@@ -36,11 +43,9 @@ module UnitMeasurements
36
43
  # system :imperial do
37
44
  # unit "in", value: "25.4 mm", aliases: ['"', "inch", "inches"]
38
45
  # end
39
- # end
40
46
  #
41
- # @param block
42
- # A block of instructions for defining units and their conversions within
43
- # the unit group.
47
+ # cache "length.json"
48
+ # end
44
49
  #
45
50
  # @yield [builder]
46
51
  # A block that defines the units to be added to the unit group.
@@ -74,10 +79,59 @@ module UnitMeasurements
74
79
  @unit_group = builder.build
75
80
  end
76
81
  end
82
+
83
+ # Returns an instance of +Configuration+ with the values of desired configurable
84
+ # options of +*unit_measurements*+. If instance is not present, it initializes
85
+ # a new instance of {Configuration}.
86
+ #
87
+ # @return [Configuration] An instance of +Configuration+.
88
+ #
89
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
90
+ # @since 5.3.0
91
+ def configuration
92
+ @configuration ||= Configuration.new
93
+ end
94
+
95
+ # Reset the configuration to its default state.
96
+ #
97
+ # @example
98
+ # UnitMeasurements.reset
99
+ #
100
+ # @return [Configuration] A new +Configuration+ object.
101
+ #
102
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
103
+ # @since 5.3.0
104
+ def reset
105
+ @configuration = Configuration.new
106
+ end
107
+
108
+ # Configures options of the +*UnitMeasurements*+ module using a block. It
109
+ # yields the current +Configuration+ instance for updating default values of
110
+ # options by new values specified within a block.
111
+ #
112
+ # @example
113
+ # UnitMeasurements.configure do |config|
114
+ # config.use_cache = false
115
+ # end
116
+ #
117
+ # @yield [configuration] The current +Configuration+ instance.
118
+ #
119
+ # @yieldparam [Configuration] configuration
120
+ # An instance of +Configuration+ with the new values of options.
121
+ #
122
+ # @yieldreturn [Configuration] The updated +Configuration+ instance.
123
+ #
124
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
125
+ # @since 5.3.0
126
+ def configure
127
+ yield configuration
128
+ end
77
129
  end
78
130
  end
79
131
 
80
132
  # The following requires load various components of the unit measurements library.
133
+ require "unit_measurements/configuration"
134
+ require "unit_measurements/cache"
81
135
  require "unit_measurements/unit_group_builder"
82
136
  require "unit_measurements/unit"
83
137
  require "unit_measurements/unit_group"
@@ -0,0 +1,173 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # -*- frozen_string_literal: true -*-
3
+ # -*- warn_indent: true -*-
4
+
5
+ module UnitMeasurements
6
+ # The +UnitMeasurements::Cache+ class manages caching of conversion factors
7
+ # between different units within a unit group. It provides methods to retrieve,
8
+ # set, and clear cached conversion factors.
9
+ #
10
+ # Cached conversion factors are stored in JSON file on the file system.
11
+ #
12
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
13
+ # @since 5.2.0
14
+ class Cache
15
+ # The directory path where cache files are stored.
16
+ #
17
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
18
+ # @since 5.2.0
19
+ CACHE_DIRECTORY = File.expand_path(File.join("..", "..", "cache"), __dir__).freeze
20
+
21
+ # Stores cached conversion factors between different units within a unit
22
+ # group.
23
+ #
24
+ # @return [Hash] The cached conversion factors.
25
+ #
26
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
27
+ # @since 5.2.0
28
+ attr_reader :cached_data
29
+
30
+ # Initializes a new +Cache+ instance for a specific unit group.
31
+ #
32
+ # Initialization first ensures existence of the cache directory. If the cache
33
+ # directory does not exist, it gets created.
34
+ #
35
+ # @param [UnitGroup] unit_group The unit group associated with the cache.
36
+ #
37
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
38
+ # @since 5.2.0
39
+ def initialize(unit_group)
40
+ ensure_cache_directory_exists
41
+ @cache_file = build_cache_file_path(unit_group)
42
+ @cached_data ||= load_cache
43
+ end
44
+
45
+ # Retrieves the conversion factor between source and target units from the
46
+ # cache.
47
+ #
48
+ # @param [String] source_unit The source unit name.
49
+ # @param [String] target_unit The target unit name.
50
+ #
51
+ # @return [Numeric|NilClass]
52
+ # The conversion factor, or +nil+ if not found in the cache.
53
+ #
54
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
55
+ # @since 5.2.0
56
+ def get(source_unit, target_unit)
57
+ cached_data.dig(source_unit, target_unit)
58
+ end
59
+
60
+ # Sets the conversion factor between source and target units in the cache.
61
+ #
62
+ # @param [String] source_unit The source unit name.
63
+ # @param [String] target_unit The target unit name.
64
+ # @param [Numeric] conversion_factor The conversion factor.
65
+ #
66
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
67
+ # @since 5.2.0
68
+ def set(source_unit, target_unit, conversion_factor)
69
+ cached_data[source_unit] ||= {}
70
+ cached_data[source_unit][target_unit] = conversion_factor
71
+
72
+ store_cache
73
+ end
74
+
75
+ # Clears the entire cache.
76
+ #
77
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
78
+ # @since 5.2.0
79
+ def clear_cache
80
+ @cached_data = {}
81
+ store_cache
82
+ end
83
+
84
+ private
85
+
86
+ # @private
87
+ # Loads the cache from the cache file.
88
+ #
89
+ # @return [Hash] The loaded cache data.
90
+ #
91
+ # @raise [Errno::ENOENT] If the cache file does not exist.
92
+ # @raise [Errno::EACCES]
93
+ # If the cache file cannot be accessed due to insufficient permissions.
94
+ # @raise [JSON::ParserError] If there's an error parsing the cache file.
95
+ #
96
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
97
+ # @since 5.2.0
98
+ def load_cache
99
+ return {} unless File.exist?(@cache_file)
100
+
101
+ begin
102
+ File.open(@cache_file, "r") { |file| JSON.load(file.read) }
103
+ rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError => e
104
+ puts "Error loading cache"
105
+ {}
106
+ end
107
+ end
108
+
109
+ # @private
110
+ # Stores the current cache data to the cache file. +cached_data+ is stored in
111
+ # prettier form.
112
+ #
113
+ # @raise [Errno::ENOENT] If the cache file does not exist.
114
+ # @raise [Errno::EACCES]
115
+ # If the cache file cannot be accessed due to insufficient permissions.
116
+ # @raise [Errno::ENOSPC]
117
+ # If there's not enough space to write to the cache file.
118
+ # @raise [JSON::GeneratorError] If there's an error generating JSON data.
119
+ #
120
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
121
+ # @since 5.2.0
122
+ def store_cache
123
+ begin
124
+ File.open(@cache_file, "w") do |file|
125
+ file.write(JSON.pretty_generate(cached_data))
126
+ end
127
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOSPC, JSON::GeneratorError => e
128
+ puts "Error saving cache: #{e.message}"
129
+ end
130
+ end
131
+
132
+ # @private
133
+ # Ensures that the cache directory exists. If the cache directory does not
134
+ # exist, it gets created.
135
+ #
136
+ # @raise [Errno::EEXIST] If the cache directory already exists.
137
+ # @raise [Errno::EACCES]
138
+ # If the cache directory cannot be accessed due to insufficient permissions.
139
+ # @raise [Errno::ENOSPC]
140
+ # If there's not enough space to create the cache directory.
141
+ #
142
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
143
+ # @since 5.2.0
144
+ def ensure_cache_directory_exists
145
+ begin
146
+ Dir.mkdir(CACHE_DIRECTORY) unless Dir.exist?(CACHE_DIRECTORY)
147
+ rescue Errno::EACCES, Errno::ENOSPC => e
148
+ puts "Error creating cache directory: #{e.message}"
149
+ end
150
+ end
151
+
152
+ # @private
153
+ # Builds and returns an absolute path of the cache file.
154
+ #
155
+ # This method first checks if the cache file name is specified for the unit
156
+ # group. If yes, it builds absolute path of the cache file using specified
157
+ # cache file name. If not, it builds file name from of the name of the unit
158
+ # group. This file name is then used to build absolute path of the cache file.
159
+ #
160
+ # @param [UnitGroup] unit_group The unit group associated with the cache.
161
+ #
162
+ # @return [String] An absolute path of the cache file.
163
+ #
164
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
165
+ # @since 5.2.0
166
+ def build_cache_file_path(unit_group)
167
+ cache_file_name = unit_group.cache_file || unit_group.to_s.split("::").last.underscore
168
+ cache_file_name = File.basename(cache_file_name, ".json") + ".json"
169
+
170
+ Pathname.new(File.join(CACHE_DIRECTORY, cache_file_name)).cleanpath
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # -*- frozen_stringing_literal: true -*-
3
+ # -*- warn_indent: true -*-
4
+
5
+ module UnitMeasurements
6
+ # The +UnitMeasurements::Configuration+ class maintains and manages the globally
7
+ # configurable options of +*unit_measurements*+.
8
+ #
9
+ # @note
10
+ # This class is responsible for configuring globally configurable options of
11
+ # +*unit_measurements*+.
12
+ #
13
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
14
+ # @since 5.3.0
15
+ class Configuration
16
+ # Get the current value of the +use_cache+ option.
17
+ #
18
+ # @note
19
+ # This option controls whether caching is enabled for converting measurements.
20
+ # Defaults to +false+.
21
+ #
22
+ # @return [TrueClass|FalseClass]
23
+ # Returns +true+ if caching is enabled, otherwise +false+.
24
+ #
25
+ # @see Cache
26
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
27
+ # @since 5.3.0
28
+ attr_reader :use_cache
29
+
30
+ # Initializes a new +Configuration+ instance with default values of configurable
31
+ # options.
32
+ #
33
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
34
+ # @since 5.3.0
35
+ def initialize
36
+ self.use_cache = false
37
+ end
38
+
39
+ # Sets a value for the +use_cache+ option.
40
+ #
41
+ # It controls whether caching is enabled for converting measurements. When
42
+ # caching is enabled, previously computed conversion factors are stored for
43
+ # future use, improving conversion performance.
44
+ #
45
+ # @param [TrueClass|FalseClass] use_cache
46
+ # +true+ if caching should be used while converting the measurement otherwise
47
+ # +false+.
48
+ #
49
+ # @return [TrueClass|FalseClass] The updated value of +use_cache+.
50
+ #
51
+ # @raise [BaseError] if +use_cache+ is not a boolean value.
52
+ #
53
+ # @see Cache
54
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
55
+ # @since 5.3.0
56
+ def use_cache=(use_cache)
57
+ unless [true, false].include?(use_cache)
58
+ raise BaseError, "Configuration#use_cache= only accepts true or false, but received #{use_cache}"
59
+ end
60
+
61
+ @use_cache = use_cache
62
+ end
63
+ end
64
+ end
@@ -120,6 +120,11 @@ module UnitMeasurements
120
120
  # Converts the measurement to a +target_unit+ and returns new instance of the
121
121
  # measurement.
122
122
  #
123
+ # When +use_cache+ value is true, conversion factor between units are checked
124
+ # in cache file of the unit group. If cached conversion factor is present in
125
+ # the cache file, it is used for conversion otherwise conversion factor is
126
+ # stored in the cache before converting the measurement to the +target_unit+.
127
+ #
123
128
  # @example
124
129
  # UnitMeasurements::Length.new(1, "m").convert_to("cm")
125
130
  # => 100.0 cm
@@ -127,9 +132,14 @@ module UnitMeasurements
127
132
  # UnitMeasurements::Length.new(1, "cm").convert_to("primitive")
128
133
  # => 0.01 m
129
134
  #
135
+ # UnitMeasurements::Length.new(1, "m").convert_to("cm", use_cache: true)
136
+ # => 100.0 cm
137
+ #
130
138
  # @param [String|Symbol] target_unit
131
139
  # The target unit for conversion. Specifing +primitive+ will convert the
132
140
  # measurement to a primitive unit of the unit group.
141
+ # @param [TrueClass|FalseClass] use_cache
142
+ # Indicates whether to use cached conversion factors.
133
143
  #
134
144
  # @return [Measurement]
135
145
  # A new +Measurement+ instance with the converted +quantity+ and
@@ -137,16 +147,15 @@ module UnitMeasurements
137
147
  #
138
148
  # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
139
149
  # @since 1.0.0
140
- def convert_to(target_unit)
150
+ def convert_to(target_unit, use_cache: false)
141
151
  target_unit = if target_unit.to_s.eql?("primitive")
142
152
  self.class.unit_group.primitive
143
153
  else
144
154
  unit_from_unit_or_name!(target_unit)
145
155
  end
146
-
147
156
  return self if target_unit == unit
148
157
 
149
- conversion_factor = (unit.conversion_factor / target_unit.conversion_factor)
158
+ conversion_factor = calculate_conversion_factor(target_unit, use_cache)
150
159
 
151
160
  self.class.new((quantity * conversion_factor), target_unit)
152
161
  end
@@ -160,7 +169,17 @@ module UnitMeasurements
160
169
  # UnitMeasurements::Length.new(1, "m").convert_to!("cm")
161
170
  # => 100.0 cm
162
171
  #
163
- # @param [String|Symbol] target_unit The target unit for conversion.
172
+ # UnitMeasurements::Length.new(1, "cm").convert_to!("primitive")
173
+ # => 0.01 m
174
+ #
175
+ # UnitMeasurements::Length.new(1, "m").convert_to!("cm", use_cache: true)
176
+ # => 100.0 cm
177
+ #
178
+ # @param [String|Symbol] target_unit
179
+ # The target unit for conversion. Specifing +primitive+ will convert the
180
+ # measurement to a primitive unit of the unit group.
181
+ # @param [TrueClass|FalseClass] use_cache
182
+ # Indicates whether to use cached conversion factors.
164
183
  #
165
184
  # @return [Measurement]
166
185
  # The current +Measurement+ instance with updated +quantity+ and +unit+.
@@ -168,8 +187,8 @@ module UnitMeasurements
168
187
  # @see #convert_to
169
188
  # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
170
189
  # @since 1.0.0
171
- def convert_to!(target_unit)
172
- measurement = convert_to(target_unit)
190
+ def convert_to!(target_unit, use_cache: false)
191
+ measurement = convert_to(target_unit, use_cache: use_cache)
173
192
  @quantity, @unit = measurement.quantity, measurement.unit
174
193
 
175
194
  self
@@ -219,16 +238,20 @@ module UnitMeasurements
219
238
  extend Forwardable
220
239
 
221
240
  # Methods delegated from the unit group.
222
- def_delegators :unit_group, :primitive, :units, :unit_names, :unit_with_name_and_aliases,
223
- :unit_names_with_aliases, :unit_for, :unit_for!, :defined?,
224
- :unit_or_alias?, :[], :units_for, :units_for!
241
+ def_delegators :unit_group, :primitive, :units, :cache_file, :unit_names,
242
+ :unit_with_name_and_aliases, :unit_names_with_aliases,
243
+ :unit_for, :unit_for!, :defined?, :unit_or_alias?, :[],
244
+ :units_for, :units_for!
225
245
 
226
246
  # Parses an input string and returns a +Measurement+ instance depending on
227
247
  # the input string. This method first normalizes the +input+ internally,
228
248
  # using the +Normalizer+ before parsing it using the +Parser+.
229
249
  #
250
+ # You can separate *source* and *target* units from each other in +input+
251
+ # using +to+, +in+, or +as+.
252
+ #
230
253
  # If only the source unit is provided, it returns a new +Measurement+
231
- # instance with the quantity in the source unit.If both source and target
254
+ # instance with the quantity in the source unit. If both source and target
232
255
  # units are provided in the input string, it returns a new +Measurement+
233
256
  # instance with the quantity converted to the target unit.
234
257
  #
@@ -237,7 +260,7 @@ module UnitMeasurements
237
260
  # => 2.0+3.0i km
238
261
  #
239
262
  # @example Parsing string representing a complex number, source, and target units:
240
- # UnitMeasurements::Length.parse("2+3i km to m")
263
+ # UnitMeasurements::Length.parse("2+3i km in m")
241
264
  # => 2000.0+3000.0i m
242
265
  #
243
266
  # @example Parsing string representing a rational or mixed rational number and source unit:
@@ -260,10 +283,10 @@ module UnitMeasurements
260
283
  # UnitMeasurements::Length.parse("2/3 km to m")
261
284
  # => 666.666666666667 m
262
285
  #
263
- # UnitMeasurements::Length.parse("2 ½ km to m")
286
+ # UnitMeasurements::Length.parse("2 ½ km in m")
264
287
  # => 2500.0 m
265
288
  #
266
- # UnitMeasurements::Length.parse("2 1/2 km to m")
289
+ # UnitMeasurements::Length.parse("2 1/2 km as m")
267
290
  # => 2500.0 m
268
291
  #
269
292
  # @example Parsing string representing a scientific number and source unit:
@@ -281,7 +304,7 @@ module UnitMeasurements
281
304
  # UnitMeasurements::Length.parse("2e+2 km to m")
282
305
  # => 200000.0 m
283
306
  #
284
- # UnitMeasurements::Length.parse("2e⁻² km to m")
307
+ # UnitMeasurements::Length.parse("2e⁻² km as m")
285
308
  # => 20.0 m
286
309
  #
287
310
  # @example Parsing string representing a ratio and source unit:
@@ -289,10 +312,12 @@ module UnitMeasurements
289
312
  # => 0.5 km
290
313
  #
291
314
  # @example Parsing string representing a ratio, source, and target units:
292
- # UnitMeasurements::Length.parse("1:2 km to m")
315
+ # UnitMeasurements::Length.parse("1:2 km in m")
293
316
  # => 500.0 m
294
317
  #
295
318
  # @param [String] input The input string to be parsed.
319
+ # @param [TrueClass|FalseClass] use_cache
320
+ # Indicates whether to use cached conversion factors.
296
321
  #
297
322
  # @return [Measurement] The +Measurement+ instance.
298
323
  #
@@ -303,11 +328,42 @@ module UnitMeasurements
303
328
  # @see #convert_to
304
329
  # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
305
330
  # @since 1.0.0
306
- def parse(input)
331
+ def parse(input, use_cache: false)
307
332
  input = Normalizer.normalize(input)
308
333
  source, target = input.match(CONVERSION_STRING_REGEXP)&.captures
309
334
 
310
- target ? _parse(source).convert_to(target) : _parse(source)
335
+ target ? _parse(source).convert_to(target, use_cache: use_cache) : _parse(source)
336
+ end
337
+
338
+ # Returns the +Cache+ instance for the unit group to store and retrieve
339
+ # conversion factors.
340
+ #
341
+ # @return [Cache] The +Cache+ instance.
342
+ #
343
+ # @example
344
+ # UnitMeasurements::Length.cached
345
+ # => #<UnitMeasurements::Cache:0x00007fe407249750>
346
+ #
347
+ # @see Cache
348
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
349
+ # @since 5.2.0
350
+ def cached
351
+ @cached ||= Cache.new(self)
352
+ end
353
+
354
+ # Clears the cached conversion factors of the unit group.
355
+ #
356
+ # @return [void]
357
+ #
358
+ # @example
359
+ # UnitMeasurements::Length.clear_cache
360
+ #
361
+ # @see Cache#clear_cache
362
+ #
363
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
364
+ # @since 5.2.0
365
+ def clear_cache
366
+ cached.clear_cache
311
367
  end
312
368
 
313
369
  private
@@ -384,5 +440,37 @@ module UnitMeasurements
384
440
  def unit_from_unit_or_name!(value)
385
441
  value.is_a?(Unit) ? value : self.class.send(:unit_group).unit_for!(value)
386
442
  end
443
+
444
+ # Calculates the conversion factor between the current unit and the target
445
+ # unit.
446
+ #
447
+ # If caching is enabled and a cached factor is available, it will be used.
448
+ # Otherwise, the conversion factor will be computed and, if caching is
449
+ # enabled, stored in the cache.
450
+ #
451
+ # @param [Unit] target_unit The target unit for conversion.
452
+ # @param [TrueClass|FalseClass] use_cache
453
+ # Indicates whether caching should be used.
454
+ #
455
+ # @return [Numeric] The conversion factor.
456
+ #
457
+ # @see Unit
458
+ # @see #convert_to
459
+ #
460
+ # @note If caching is enabled, the calculated conversion factor will be stored in the cache.
461
+ #
462
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
463
+ # @since 5.2.0
464
+ def calculate_conversion_factor(target_unit, use_cache)
465
+ use_cache = (UnitMeasurements.configuration.use_cache || use_cache)
466
+
467
+ if use_cache && (cached_factor = self.class.cached.get(unit.name, target_unit.name))
468
+ cached_factor
469
+ else
470
+ factor = unit.conversion_factor / target_unit.conversion_factor
471
+ self.class.cached.set(unit.name, target_unit.name, factor) if use_cache
472
+ factor
473
+ end
474
+ end
387
475
  end
388
476
  end
@@ -36,16 +36,30 @@ module UnitMeasurements
36
36
  # @since 1.0.0
37
37
  attr_reader :units
38
38
 
39
+ # The name of the cache file.
40
+ #
41
+ # @example
42
+ # UnitMeasurements::Length.cache_file
43
+ # => "length.json"
44
+ #
45
+ # @return [String] The name of the cache file.
46
+ #
47
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
48
+ # @since 5.2.0
49
+ attr_reader :cache_file
50
+
39
51
  # Initializes a new +UnitGroup+ instance.
40
52
  #
41
53
  # @param [String|Symbol, optional] primitive The name of the primitive unit.
42
54
  # @param [Array<Unit>] units An array of +Unit+ instances.
55
+ # @param [String] cache_file The name of the cache file.
43
56
  #
44
57
  # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
45
58
  # @since 1.0.0
46
- def initialize(primitive, units)
59
+ def initialize(primitive, units, cache_file)
47
60
  @units = units.map { |unit| unit.with(unit_group: self) }
48
61
  @primitive = unit_for!(primitive) if primitive
62
+ @cache_file = cache_file
49
63
  end
50
64
 
51
65
  # Returns the unit instance for a given unit name. It returns +nil+ if unit
@@ -82,7 +82,7 @@ module UnitMeasurements
82
82
  # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
83
83
  # @since 1.0.0
84
84
  def build
85
- UnitGroup.new(@primitive, @units)
85
+ UnitGroup.new(@primitive, @units, @cache_file)
86
86
  end
87
87
 
88
88
  # Defines the +unit system+ within the unit group and evaluates the provided
@@ -130,6 +130,19 @@ module UnitMeasurements
130
130
  @primitive = primitive
131
131
  end
132
132
 
133
+ # Sets the name of the cache file for the unit group.
134
+ #
135
+ # @example
136
+ # cache "conversion_cache.json"
137
+ #
138
+ # @param [String] cache_file The name of the cache file.
139
+ #
140
+ # @author {Harshal V. Ladhe}[https://shivam091.github.io/]
141
+ # @since 5.2.0
142
+ def cache(cache_file)
143
+ @cache_file = cache_file
144
+ end
145
+
133
146
  private
134
147
 
135
148
  # @private
@@ -4,5 +4,5 @@
4
4
 
5
5
  module UnitMeasurements
6
6
  # Current stable version.
7
- VERSION = "5.1.1"
7
+ VERSION = "5.3.0"
8
8
  end
data/units.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # Bundled unit groups and units
2
2
 
3
- As there are lots of units bundled with `unit_measurements`, we recommend you to check below list of
4
- bundled units before converting your measurements.
3
+ As there are lots of units bundled with `unit_measurements`, we recommend you to
4
+ check below list of bundled units before converting your measurements.
5
5
 
6
6
  **Notes:**
7
- 1. Unit names suffixed with `*` support all [Decimal SI prefixes](README.md#decimal-si-prefixes).
8
- 2. Unit names suffixed with `**` support all [Decimal SI prefixes](README.md#decimal-si-prefixes)
9
- and [Binary SI prefixes](README.md#binary-si-prefixes).
7
+ 1. Unit names suffixed with `*` support [Decimal SI prefixes](README.md#decimal-si-prefixes).
8
+ 2. Unit names suffixed with `**` support [Binary SI prefixes](README.md#binary-si-prefixes)
9
+ in addition to [Decimal SI prefixes](README.md#decimal-si-prefixes).
10
10
  3. Primitive unit of the unit group is in _emphasised typeface_.
11
11
 
12
12
  ## 1. Length/Distance
@@ -125,7 +125,7 @@ These units are defined in `UnitMeasurements::Area`.
125
125
  | 12 | fur² | fur^2, sq fur, square furlong, square furlongs |
126
126
  | 13 | rod² | rod^2, sq rod, square rod, square rods |
127
127
 
128
- ## 9. Volume
128
+ ## 9. Volume & Capacity
129
129
 
130
130
  These units are defined in `UnitMeasurements::Volume`.
131
131
 
@@ -328,7 +328,7 @@ These units are defined in `UnitMeasurements::ElectricalElastance`.
328
328
  | _1_ | _D*_ | _F⁻¹, daraf, darafs, reciprocal farad, reciprocal farads_ |
329
329
  | 2 | V/C | V·C⁻¹, volt/coulomb, volts/coulomb, volt per coulomb, volts per coulomb |
330
330
 
331
- ## 24. Electrical resistance
331
+ ## 24. Electrical resistance/Impedance
332
332
 
333
333
  These units are defined in `UnitMeasurements::ElectricalResistance`.
334
334
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unit_measurements
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harshal LADHE
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-20 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -107,7 +107,9 @@ files:
107
107
  - lib/unit_measurements.rb
108
108
  - lib/unit_measurements/arithmetic.rb
109
109
  - lib/unit_measurements/base.rb
110
+ - lib/unit_measurements/cache.rb
110
111
  - lib/unit_measurements/comparison.rb
112
+ - lib/unit_measurements/configuration.rb
111
113
  - lib/unit_measurements/conversion.rb
112
114
  - lib/unit_measurements/errors/parse_error.rb
113
115
  - lib/unit_measurements/errors/primitive_unit_already_set_error.rb