basic_temperature 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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