basic_temperature 0.2.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.inch.yml +3 -0
  3. data/.rubocop.yml +3 -0
  4. data/CHANGELOG.md +68 -7
  5. data/README.md +41 -61
  6. data/bin/console +1 -1
  7. data/docs/classes/BasicTemperature.html +27 -1129
  8. data/docs/classes/BasicTemperature/Temperature.html +1239 -0
  9. data/docs/classes/BasicTemperature/Temperature/AdditionalHelpers.html +78 -0
  10. data/docs/classes/BasicTemperature/Temperature/Assertions.html +78 -0
  11. data/docs/classes/BasicTemperature/Temperature/Casting.html +78 -0
  12. data/docs/classes/BasicTemperature/Temperature/Errors.html +109 -0
  13. data/docs/classes/BasicTemperature/Temperature/Errors/InitializationArguments.html +94 -0
  14. data/docs/classes/BasicTemperature/Temperature/Errors/InvalidDegrees.html +94 -0
  15. data/docs/classes/BasicTemperature/Temperature/Errors/InvalidNumeric.html +94 -0
  16. data/docs/classes/BasicTemperature/Temperature/Errors/InvalidNumericOrTemperature.html +94 -0
  17. data/docs/classes/BasicTemperature/Temperature/Errors/InvalidScale.html +94 -0
  18. data/docs/classes/BasicTemperature/Temperature/Initialization.html +78 -0
  19. data/docs/classes/BasicTemperature/Temperature/Memoization.html +78 -0
  20. data/docs/classes/BasicTemperature/Temperature/Rounding.html +78 -0
  21. data/docs/classes/Object.html +3 -175
  22. data/docs/created.rid +12 -4
  23. data/docs/files/lib/basic_temperature/alias_rb.html +73 -0
  24. data/docs/files/lib/basic_temperature/temperature/additional_helpers_rb.html +94 -0
  25. data/docs/files/lib/basic_temperature/temperature/assertions_rb.html +94 -0
  26. data/docs/files/lib/basic_temperature/temperature/casting_rb.html +94 -0
  27. data/docs/files/lib/basic_temperature/temperature/errors_rb.html +119 -0
  28. data/docs/files/lib/basic_temperature/temperature/initialization_rb.html +94 -0
  29. data/docs/files/lib/basic_temperature/temperature/memoization_rb.html +94 -0
  30. data/docs/files/lib/basic_temperature/temperature/rounding_rb.html +94 -0
  31. data/docs/files/lib/basic_temperature/temperature_rb.html +17 -1
  32. data/docs/files/lib/basic_temperature/version_rb.html +2 -2
  33. data/docs/files/lib/basic_temperature_rb.html +2 -30
  34. data/docs/js/navigation.js.gz +0 -0
  35. data/docs/js/search_index.js +1 -1
  36. data/docs/js/search_index.js.gz +0 -0
  37. data/docs/js/searcher.js.gz +0 -0
  38. data/docs/panel/links.html +16 -0
  39. data/docs/panel/tree.js +1 -1
  40. data/lib/basic_temperature.rb +3 -700
  41. data/lib/basic_temperature/alias.rb +5 -0
  42. data/lib/basic_temperature/temperature.rb +515 -2
  43. data/lib/basic_temperature/temperature/additional_helpers.rb +17 -0
  44. data/lib/basic_temperature/temperature/assertions.rb +30 -0
  45. data/lib/basic_temperature/temperature/casting.rb +19 -0
  46. data/lib/basic_temperature/temperature/errors.rb +50 -0
  47. data/lib/basic_temperature/temperature/initialization.rb +32 -0
  48. data/lib/basic_temperature/temperature/memoization.rb +26 -0
  49. data/lib/basic_temperature/temperature/rounding.rb +13 -0
  50. data/lib/basic_temperature/version.rb +2 -2
  51. metadata +32 -2
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../basic_temperature'
4
+
5
+ Temperature = BasicTemperature::Temperature
@@ -1,5 +1,518 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../basic_temperature'
3
+ require_relative 'temperature/additional_helpers'
4
+ require_relative 'temperature/assertions'
5
+ require_relative 'temperature/casting'
6
+ require_relative 'temperature/errors'
7
+ require_relative 'temperature/initialization'
8
+ require_relative 'temperature/memoization'
9
+ require_relative 'temperature/rounding'
4
10
 
5
- Temperature = BasicTemperature
11
+ module BasicTemperature
12
+ # rubocop:disable Metrics/ClassLength
13
+
14
+ ##
15
+ # Temperature is a simple {Value Object}[https://martinfowler.com/bliki/ValueObject.html] for basic
16
+ # temperature operations like conversions from <tt>Celsius</tt> to <tt>Fahrenhait</tt> or <tt>Kelvin</tt>
17
+ # etc.
18
+ #
19
+ # Supported scales: <tt>Celsius</tt>, <tt>Fahrenheit</tt>, <tt>Kelvin</tt> and <tt>Rankine</tt>.
20
+ #
21
+ # == Creating Temperatures
22
+ #
23
+ # A new temperature can be created in multiple ways:
24
+ #
25
+ # - Using keyword arguments:
26
+ #
27
+ # Temperature.new(degrees: 0, scale: :celsius)
28
+ #
29
+ # - Using positional arguments:
30
+ #
31
+ # Temperature.new(0, :celsius)
32
+ #
33
+ # - Even more concise way using <tt>Temperature.[]</tt> (an alias of <tt>Temperature.new</tt>):
34
+ #
35
+ # Temperature[0, :celsius]
36
+ #
37
+ #
38
+ # == Creating Temperatures from already existing temperature objects
39
+ #
40
+ # Sometimes it is useful to create a new temperature from already existing one.
41
+ #
42
+ # For such cases, there are {set_degrees}[rdoc-ref:Temperature#set_degrees] and
43
+ # {set_scale}[rdoc-ref:Temperature#set_scale].
44
+ #
45
+ # Since temperatures are {Value Objects}[https://martinfowler.com/bliki/ValueObject.html], both methods
46
+ # returns new instances.
47
+ #
48
+ # Examples:
49
+ #
50
+ # temperature = Temperature[0, :celsius]
51
+ # # => 0 °C
52
+ #
53
+ # new_temperature = temperature.set_degrees(15)
54
+ # # => 15 °C
55
+ #
56
+ # temperature = Temperature[0, :celsius]
57
+ # # => 0 °C
58
+ #
59
+ # new_temperature = temperature.set_scale(:kelvin)
60
+ # # => 0 K
61
+ #
62
+ # == Conversions
63
+ #
64
+ # Temperatures can be converted to diffirent scales.
65
+ #
66
+ # Currently, the following scales are supported: <tt>Celsius</tt>, <tt>Fahrenheit</tt>, <tt>Kelvin</tt> and
67
+ # <tt>Rankine</tt>.
68
+ #
69
+ # Temperature[20, :celsius].to_celsius
70
+ # # => 20 °C
71
+ #
72
+ # Temperature[20, :celsius].to_fahrenheit
73
+ # # => 68 °F
74
+ #
75
+ # Temperature[20, :celsius].to_kelvin
76
+ # # => 293.15 K
77
+ #
78
+ # Temperature[20, :celsius].to_rankine
79
+ # # => 527.67 °R
80
+ #
81
+ # If it is necessary to convert scale dynamically, {to_scale}[rdoc-ref:Temperature#to_scale] method is
82
+ # available.
83
+ #
84
+ # Temperature[20, :celsius].to_scale(scale)
85
+ #
86
+ # All conversion formulas are taken from
87
+ # {RapidTables}[https://www.rapidtables.com/convert/temperature/index.html].
88
+ #
89
+ # Conversion precision: 2 accurate digits after the decimal dot.
90
+ #
91
+ # == Comparison
92
+ #
93
+ # Temperature implements idiomatic {<=> spaceship operator}[https://ruby-doc.org/core/Comparable.html] and
94
+ # mixes in {Comparable}[https://ruby-doc.org/core/Comparable.html] module.
95
+ #
96
+ # As a result, all methods from Comparable are available, e.g:
97
+ #
98
+ # Temperature[20, :celsius] < Temperature[25, :celsius]
99
+ # # => true
100
+ #
101
+ # Temperature[20, :celsius] <= Temperature[25, :celsius]
102
+ # # => true
103
+ #
104
+ # Temperature[20, :celsius] == Temperature[25, :celsius]
105
+ # # => false
106
+ #
107
+ # Temperature[20, :celsius] > Temperature[25, :celsius]
108
+ # # => false
109
+ #
110
+ # Temperature[20, :celsius] >= Temperature[25, :celsius]
111
+ # # => false
112
+ #
113
+ # Temperature[20, :celsius].between?(Temperature[15, :celsius], Temperature[25, :celsius])
114
+ # # => true
115
+ #
116
+ # # Starting from Ruby 2.4.6
117
+ # Temperature[20, :celsius].clamp(Temperature[20, :celsius], Temperature[25, :celsius])
118
+ # # => 20 °C
119
+ #
120
+ # Please note, if <tt>other</tt> temperature has a different scale, temperature is automatically converted
121
+ # to that scale before comparison.
122
+ #
123
+ # Temperature[20, :celsius] == Temperature[293.15, :kelvin]
124
+ # # => true
125
+ #
126
+ # IMPORTANT !!!
127
+ #
128
+ # <tt>degrees</tt> are rounded to the nearest value with a precision of 2 decimal digits before comparison.
129
+ #
130
+ # This means the following temperatures are considered as equal:
131
+ #
132
+ # Temperature[20.020, :celsius] == Temperature[20.024, :celsius]
133
+ # # => true
134
+ #
135
+ # Temperature[20.025, :celsius] == Temperature[20.029, :celsius]
136
+ # # => true
137
+ #
138
+ # while these ones are treated as NOT equal:
139
+ #
140
+ # Temperature[20.024, :celsius] == Temperature[20.029, :celsius]
141
+ # # => false
142
+ #
143
+ # == Math
144
+ #
145
+ # ==== Addition/Subtraction.
146
+ #
147
+ # Temperature[20, :celsius] + Temperature[10, :celsius]
148
+ # # => 30 °C
149
+ #
150
+ # Temperature[20, :celsius] - Temperature[10, :celsius]
151
+ # # => 10 °C
152
+ #
153
+ # If second temperature has a different scale, first temperature is automatically converted to that scale
154
+ # before <tt>degrees</tt> addition/subtraction.
155
+ #
156
+ # Temperature[283.15, :kelvin] + Temperature[10, :celsius]
157
+ # # => 10 °C
158
+ #
159
+ # Returned temperature will have the same scale as the second temperature.
160
+ #
161
+ # It is possible to add/subtract numerics.
162
+ #
163
+ # Temperature[20, :celsius] + 10
164
+ # # => 30 °C
165
+ #
166
+ # Temperature[20, :celsius] - 10
167
+ # # => 10 °C
168
+ #
169
+ # In such cases, returned temperature will have the same scale as the first temperature.
170
+ #
171
+ # Also {Ruby coersion mechanism}[https://ruby-doc.org/core/Numeric.html#method-i-coerce] is supported.
172
+ #
173
+ # 10 + Temperature[20, :celsius]
174
+ # # => 30 °C
175
+ #
176
+ # 10 - Temperature[20, :celsius]
177
+ # # => -10 °C
178
+ #
179
+ # ==== Negation
180
+ #
181
+ # -Temperature[20, :celsius]
182
+ # # => -20 °C
183
+ #
184
+ # == Queries
185
+ #
186
+ # Temperature[0, :celsius].boil_water?
187
+ # # => false
188
+ #
189
+ # Temperature[0, :celsius].freeze_water?
190
+ # # => true
191
+ #
192
+ class Temperature
193
+ include Comparable
194
+
195
+ include AdditionalHelpers
196
+ include Assertions
197
+ include Casting
198
+ include Errors
199
+ include Initialization
200
+ include Memoization
201
+ include Rounding
202
+
203
+ CELSIUS = 'celsius'
204
+ FAHRENHEIT = 'fahrenheit'
205
+ KELVIN = 'kelvin'
206
+ RANKINE = 'rankine'
207
+
208
+ # A list of all currently supported scale values.
209
+ SCALES = [CELSIUS, FAHRENHEIT, KELVIN, RANKINE].freeze
210
+
211
+ # Degrees of the temperature.
212
+ attr_reader :degrees
213
+
214
+ # Scale of the temperature. Look at {SCALES}[rdoc-ref:Temperature::SCALES] for possible values.
215
+ attr_reader :scale
216
+
217
+ ##
218
+ # Creates a new instance of Temperature. Alias for <tt>new</tt>.
219
+ #
220
+ # :call-seq:
221
+ # [](degrees:, scale:)
222
+ # [](degrees, scale)
223
+ #
224
+ def self.[](*args, **kwargs)
225
+ new(*args, **kwargs)
226
+ end
227
+
228
+ ##
229
+ # Creates a new instance of Temperature. Is aliased as <tt>[]</tt>.
230
+ #
231
+ # :call-seq:
232
+ # new(degrees:, scale:)
233
+ # new(degrees, scale)
234
+ #
235
+ def initialize(*positional_arguments, **keyword_arguments)
236
+ assert_either_positional_arguments_or_keyword_arguments!(positional_arguments, keyword_arguments)
237
+
238
+ if keyword_arguments.any?
239
+ initialize_via_keywords_arguments(keyword_arguments)
240
+ else # positional_arguments.any?
241
+ initialize_via_positional_arguments(positional_arguments)
242
+ end
243
+ end
244
+
245
+ # rubocop:disable Naming/AccessorMethodName
246
+
247
+ # Returns a new Temperature with updated <tt>degrees</tt>.
248
+ #
249
+ # temperature = Temperature[0, :celsius]
250
+ # # => 0 °C
251
+ #
252
+ # new_temperature = temperature.set_degrees(15)
253
+ # # => 15 °C
254
+ #
255
+ def set_degrees(degrees)
256
+ Temperature.new(degrees, scale)
257
+ end
258
+ # rubocop:enable Naming/AccessorMethodName
259
+
260
+ # rubocop:disable Naming/AccessorMethodName
261
+
262
+ # Returns a new Temperature with updated <tt>scale</tt>.
263
+ #
264
+ # temperature = Temperature[0, :celsius]
265
+ # # => 0 °C
266
+ #
267
+ # new_temperature = temperature.set_scale(:kelvin)
268
+ # # => 0 K
269
+ #
270
+ def set_scale(scale)
271
+ Temperature.new(degrees, scale)
272
+ end
273
+ # rubocop:enable Naming/AccessorMethodName
274
+
275
+ ##
276
+ # Converts temperature to specific <tt>scale</tt>.
277
+ # If temperature is already in desired <tt>scale</tt>, returns current temperature object.
278
+ #
279
+ # Raises {InvalidScaleError}[rdoc-ref:Temperature::InvalidScaleError]
280
+ # when <tt>scale</tt> can not be casted to any possible scale value
281
+ # (see {SCALES}[rdoc-ref:Temperature::SCALES]).
282
+ #
283
+ # Temperature[60, :fahrenheit].to_scale(:celsius)
284
+ # # => 15.56 °C
285
+ #
286
+ def to_scale(scale)
287
+ casted_scale = cast_scale(scale)
288
+
289
+ assert_valid_scale!(casted_scale)
290
+
291
+ case casted_scale
292
+ when CELSIUS
293
+ to_celsius
294
+ when FAHRENHEIT
295
+ to_fahrenheit
296
+ when KELVIN
297
+ to_kelvin
298
+ when RANKINE
299
+ to_rankine
300
+ end
301
+ end
302
+
303
+ ##
304
+ # Converts temperature to Celsius scale. If temperature is already in Celsius, returns current
305
+ # temperature object.
306
+ #
307
+ # Memoizes subsequent calls.
308
+ #
309
+ # Conversion formulas are taken from {RapidTables}[https://www.rapidtables.com/]:
310
+ # 1. {Celsius to Fahrenheit}[https://www.rapidtables.com/convert/temperature/celsius-to-fahrenheit.html].
311
+ # 2. {Celsius to Kelvin}[https://www.rapidtables.com/convert/temperature/celsius-to-kelvin.html].
312
+ # 3. {Celsius to Rankine}[https://www.rapidtables.com/convert/temperature/celsius-to-rankine.html].
313
+ #
314
+ # Temperature[0, :fahrenheit].to_celsius
315
+ # # => -17.78 °C
316
+ #
317
+ def to_celsius
318
+ memoized(:to_celsius) || memoize(:to_celsius, -> {
319
+ return self if self.scale == CELSIUS
320
+
321
+ degrees =
322
+ case self.scale
323
+ when FAHRENHEIT
324
+ (self.degrees - 32) * (5 / 9r)
325
+ when KELVIN
326
+ self.degrees - 273.15
327
+ when RANKINE
328
+ (self.degrees - 491.67) * (5 / 9r)
329
+ end
330
+
331
+ Temperature.new(degrees, CELSIUS)
332
+ })
333
+ end
334
+
335
+ ##
336
+ # Converts temperature to Fahrenheit scale. If temperature is already in Fahrenheit, returns current
337
+ # temperature object.
338
+ #
339
+ # Memoizes subsequent calls.
340
+ #
341
+ # Conversion formulas are taken from {RapidTables}[https://www.rapidtables.com/]:
342
+ # 1. {Fahrenheit to Celsius}[https://www.rapidtables.com/convert/temperature/fahrenheit-to-celsius.html].
343
+ # 2. {Fahrenheit to Kelvin}[https://www.rapidtables.com/convert/temperature/fahrenheit-to-kelvin.html].
344
+ # 3. {Fahrenheit to Rankine}[https://www.rapidtables.com/convert/temperature/fahrenheit-to-rankine.html].
345
+ #
346
+ # Temperature[0, :celsius].to_fahrenheit
347
+ # # => 32 °F
348
+ #
349
+ def to_fahrenheit
350
+ memoized(:to_fahrenheit) || memoize(:to_fahrenheit, -> {
351
+ return self if self.scale == FAHRENHEIT
352
+
353
+ degrees =
354
+ case self.scale
355
+ when CELSIUS
356
+ self.degrees * (9 / 5r) + 32
357
+ when KELVIN
358
+ self.degrees * (9 / 5r) - 459.67
359
+ when RANKINE
360
+ self.degrees - 459.67
361
+ end
362
+
363
+ Temperature.new(degrees, FAHRENHEIT)
364
+ })
365
+ end
366
+
367
+ ##
368
+ # Converts temperature to Kelvin scale. If temperature is already in Kelvin, returns current
369
+ # temperature object.
370
+ #
371
+ # Memoizes subsequent calls.
372
+ #
373
+ # Conversion formulas are taken from {RapidTables}[https://www.rapidtables.com/]:
374
+ # 1. {Kelvin to Celsius}[https://www.rapidtables.com/convert/temperature/kelvin-to-celsius.html].
375
+ # 2. {Kelvin to Fahrenheit}[https://www.rapidtables.com/convert/temperature/kelvin-to-fahrenheit.html].
376
+ # 3. {Kelvin to Rankine}[https://www.rapidtables.com/convert/temperature/kelvin-to-rankine.html].
377
+ #
378
+ # Temperature[0, :kelvin].to_rankine
379
+ # # => 0 °R
380
+ #
381
+ def to_kelvin
382
+ memoized(:to_kelvin) || memoize(:to_kelvin, -> {
383
+ return self if self.scale == KELVIN
384
+
385
+ degrees =
386
+ case self.scale
387
+ when CELSIUS
388
+ self.degrees + 273.15
389
+ when FAHRENHEIT
390
+ (self.degrees + 459.67) * (5 / 9r)
391
+ when RANKINE
392
+ self.degrees * (5 / 9r)
393
+ end
394
+
395
+ Temperature.new(degrees, KELVIN)
396
+ })
397
+ end
398
+
399
+ ##
400
+ # Converts temperature to Rankine scale. If temperature is already in Rankine, returns current
401
+ # temperature object.
402
+ #
403
+ # Memoizes subsequent calls.
404
+ #
405
+ # Conversion formulas are taken from {RapidTables}[https://www.rapidtables.com/]:
406
+ # 1. {Rankine to Celsius}[https://www.rapidtables.com/convert/temperature/rankine-to-celsius.html].
407
+ # 2. {Rankine to Fahrenheit}[https://www.rapidtables.com/convert/temperature/rankine-to-fahrenheit.html].
408
+ # 3. {Rankine to Kelvin}[https://www.rapidtables.com/convert/temperature/rankine-to-kelvin.html].
409
+ #
410
+ # Temperature[0, :rankine].to_kelvin
411
+ # # => 0 K
412
+ #
413
+ def to_rankine
414
+ memoized(:to_rankine) || memoize(:to_rankine, -> {
415
+ return self if self.scale == RANKINE
416
+
417
+ degrees =
418
+ case self.scale
419
+ when CELSIUS
420
+ (self.degrees + 273.15) * (9 / 5r)
421
+ when FAHRENHEIT
422
+ self.degrees + 459.67
423
+ when KELVIN
424
+ self.degrees * (9 / 5r)
425
+ end
426
+
427
+ Temperature.new(degrees, RANKINE)
428
+ })
429
+ end
430
+
431
+ ##
432
+ # Compares temperture with <tt>other</tt> temperature.
433
+ #
434
+ # Returns <tt>0</tt> if they are considered as equal.
435
+ #
436
+ # Two temperatures are considered as equal when they have the same amount of <tt>degrees</tt>.
437
+ #
438
+ # Returns <tt>-1</tt> if temperature is lower than <tt>other</tt> temperature.
439
+ #
440
+ # Returns <tt>1</tt> if temperature is higher than <tt>other</tt> temperature.
441
+ #
442
+ # If <tt>other</tt> temperature has a different scale, temperature is automatically converted to that
443
+ # scale before <tt>degrees</tt> comparison.
444
+ #
445
+ # Temperature[20, :celsius] <=> Temperature[20, :celsius]
446
+ # # => 0
447
+ #
448
+ # Temperature[20, :celsius] <=> Temperature[293.15, :kelvin]
449
+ # # => 0
450
+ #
451
+ # IMPORTANT!!!
452
+ #
453
+ # This method rounds <tt>degrees</tt> to the nearest value with a precision of 2 decimal digits.
454
+ #
455
+ # This means the following:
456
+ #
457
+ # Temperature[20.020, :celsius] <=> Temperature[20.024, :celsius]
458
+ # # => 0
459
+ #
460
+ # Temperature[20.025, :celsius] <=> Temperature[20.029, :celsius]
461
+ # # => 0
462
+ #
463
+ # Temperature[20.024, :celsius] <=> Temperature[20.029, :celsius]
464
+ # # => -1
465
+ #
466
+ def <=>(other)
467
+ return unless assert_temperature(other)
468
+
469
+ round_degrees(self.to_scale(other.scale).degrees) <=> round_degrees(other.degrees)
470
+ end
471
+
472
+ ##
473
+ # Returns true when temperature boils water (is greater than or equal to 100 °C),
474
+ # false otherwise.
475
+ #
476
+ def boil_water?
477
+ self.to_celsius.degrees >= 100
478
+ end
479
+
480
+ ##
481
+ # Returns true when temperature freezes water (is less than or equal to 0 °C),
482
+ # false otherwise.
483
+ #
484
+ def freeze_water?
485
+ self.to_celsius.degrees <= 0
486
+ end
487
+
488
+ # Is used by {+}[rdoc-ref:Temperature#+] and {-}[rdoc-ref:Temperature#-]
489
+ # for {Ruby coersion mechanism}[https://ruby-doc.org/core/Numeric.html#method-i-coerce].
490
+ def coerce(numeric) #:nodoc:
491
+ assert_numeric!(numeric)
492
+
493
+ [Temperature.new(numeric, self.scale), self]
494
+ end
495
+
496
+ # Returns a string containing a human-readable representation of temperature.
497
+ def inspect #:nodoc:
498
+ rounded_degrees = round_degrees(degrees)
499
+
500
+ printable_degrees = degrees_without_decimal?(rounded_degrees) ? rounded_degrees.to_i : rounded_degrees
501
+
502
+ scale_symbol =
503
+ case self.scale
504
+ when CELSIUS
505
+ '°C'
506
+ when FAHRENHEIT
507
+ '°F'
508
+ when KELVIN
509
+ 'K'
510
+ when RANKINE
511
+ '°R'
512
+ end
513
+
514
+ "#{printable_degrees} #{scale_symbol}"
515
+ end
516
+ end
517
+ # rubocop:enable Metrics/ClassLength
518
+ end