• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

marian13 / semantic_boolean / 15864426769

25 Jun 2025 12:31AM UTC coverage: 93.782% (+1.4%) from 92.424%
15864426769

push

github

marian13
test(semantic_boolean): add more specs

81 of 90 branches covered (90.0%)

Branch coverage included in aggregate %.

100 of 103 relevant lines covered (97.09%)

838.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.78
/lib/semantic_boolean.rb
1
# frozen_string_literal: true
2

3
##
4
# @author Marian Kostyk <mariankostyk13895@gmail.com>
5
# @license MIT <https://opensource.org/license/mit>
6
##
7

8
require_relative "semantic_boolean/version"
14✔
9

10
require "set"
14✔
11

12
# rubocop:disable Lint/BooleanSymbol
13
module SemanticBoolean
14✔
14
  class << self
14✔
15
    ##
16
    # Truthy values in `SemanticBoolean.to_env_bool` terms.
17
    #
18
    # @api private
19
    # @return [Array<String>]
20
    #
21
    TO_ENV_BOOL_TRUE_VALUES = ["t", "T", "true", "True", "TRUE", "on", "On", "ON", "y", "Y", "yes", "Yes", "YES"].freeze
14✔
22

23
    ##
24
    # Falsy values in `ActiveModel::Type::Boolean` terms.
25
    #
26
    # @api private
27
    # @return [Array<String>]
28
    #
29
    # @see https://github.com/rails/rails/blob/v8.0.2/activemodel/lib/active_model/type/boolean.rb#L15
30
    #
31
    TO_ACTIVE_MODEL_BOOLEAN_TYPE_FALSE_VALUES = [false, 0, "0", :"0", "f", :f, "F", :F, "false", :false, "FALSE", :FALSE, "off", :off, "OFF", :OFF].to_set.freeze
14✔
32

33
    ##
34
    # Regexp to match falsy string values in Rails `blank?` terms.
35
    #
36
    # @api private
37
    # @return [Regexp]
38
    #
39
    # @see https://github.com/rails/rails/blob/v8.0.2/activesupport/lib/active_support/core_ext/object/blank.rb#L136
40
    #
41
    ACTIVE_SUPPORT_CORE_EXT_BLANK_RE = /\A[[:space:]]*\z/
14✔
42

43
    ##
44
    # Cache of regexp objects to match falsy string values in Rails `blank?` terms with non-default encodings.
45
    #
46
    # @api private
47
    # @return [Hash]
48
    #
49
    # @see https://github.com/rails/rails/blob/v8.0.2/activesupport/lib/active_support/core_ext/object/blank.rb#L137
50
    #
51
    ACTIVE_SUPPORT_CORE_EXT_ENCODED_BLANKS = ::Hash.new do |h, enc|
14✔
52
      h[enc] = ::Regexp.new(ACTIVE_SUPPORT_CORE_EXT_BLANK_RE.source.encode(enc), ACTIVE_SUPPORT_CORE_EXT_BLANK_RE.options | ::Regexp::FIXEDENCODING)
52✔
53
    end
54

55
    ##
56
    # Returns `true` when `object` is `true` or `false`, returns `false` for all the other cases.
57
    # @api public
58
    # @param object [Object] Can be any type.
59
    # @return [Boolean]
60
    #
61
    def boolean?(object)
14✔
62
      return true if object == true
56✔
63
      return true if object == false
42✔
64

65
      false
28✔
66
    end
67

68
    ##
69
    # Returns `true` when `object` is `true`, returns `false` for all the other cases.
70
    # @api public
71
    # @param object [Object] Can be any type.
72
    # @return [Boolean]
73
    #
74
    def true?(object)
14✔
75
      return true if object == true
56✔
76

77
      false
42✔
78
    end
79

80
    ##
81
    # Returns `true` when `object` is `false`, returns `false` for all the other cases.
82
    # @api public
83
    # @param object [Object] Can be any type.
84
    # @return [Boolean]
85
    #
86
    def false?(object)
14✔
87
      return true if object == false
56✔
88

89
      false
42✔
90
    end
91

92
    ##
93
    # Converts `object` to boolean using exactly the same logic as `blank?` in Rails does (but with `Hash` instead of `Concurent::Map` for string encodings storage).
94
    #
95
    # @api public
96
    # @param object [Object] Can be any type.
97
    # @return [Boolean]
98
    #
99
    # @note If performance is a concern, prefer to load Rails (or just `activesupport`) and use `blank?` directly.
100
    # @see https://api.rubyonrails.org/classes/Object.html#method-i-blank-3F
101
    # @see https://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html#method-i-blank-3F
102
    # @see https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-blank-3F
103
    #
104
    def blank?(object)
14✔
105
      respond_to_blank =
1,906✔
106
        begin
107
          object.respond_to?(:blank?)
4,452✔
108
        rescue ::NoMethodError
109
          object.blank? # Only `BasicObject` does NOT respond to `respond_to?`.
14✔
110
        end
111

112
      return object.__send__(:blank?) if respond_to_blank
4,438✔
113

114
      case object
3,790✔
115
      when ::NilClass
9✔
116
        true
14✔
117
      when ::FalseClass
54✔
118
        true
84✔
119
      when ::TrueClass
54✔
120
        false
84✔
121
      when ::Array
18✔
122
        object.empty?
28✔
123
      when ::Hash
18✔
124
        object.empty?
28✔
125
      when ::Symbol
342✔
126
        object.empty?
532✔
127
      when ::String
2,087✔
128
        object.empty? ||
3,019✔
129
          begin
130
            ACTIVE_SUPPORT_CORE_EXT_BLANK_RE.match?(object)
3,234✔
131
          rescue ::Encoding::CompatibilityError
132
            ACTIVE_SUPPORT_CORE_EXT_ENCODED_BLANKS[object.encoding].match?(object)
112✔
133
          end
229✔
134
      when ::Numeric
144✔
135
        false
224✔
136
      when ::Time
9✔
137
        false
14✔
138
      when ::Object
108✔
139
        object.respond_to?(:empty?) ? !!object.empty? : false
172✔
140
      else
×
141
        object.blank?
×
142
      end
143
    end
144

145
    ##
146
    # Converts `object` to boolean using exactly the same logic as `present?` in Rails does (but with `Hash` instead of `Concurent::Map` for string encodings storage).
147
    #
148
    # @api public
149
    # @param object [Object] Can be any type.
150
    # @return [Boolean]
151
    #
152
    # @note If performance is a concern, prefer to load Rails (or just `activesupport`) and use `present?` directly.
153
    # @see https://api.rubyonrails.org/classes/Object.html#method-i-present-3F
154
    #
155
    def present?(object)
14✔
156
      respond_to_present =
1,906✔
157
        begin
158
          object.respond_to?(:present?)
4,452✔
159
        rescue ::NoMethodError
160
          object.present? # Only `BasicObject` does NOT respond to `respond_to?`.
14✔
161
        end
162

163
      return object.__send__(:present?) if respond_to_present
4,438✔
164
      return !object.__send__(:blank?) if object.respond_to?(:blank?)
4,425✔
165

166
      case object
3,778✔
167
      when ::NilClass
9✔
168
        false
14✔
169
      when ::FalseClass
54✔
170
        false
84✔
171
      when ::TrueClass
54✔
172
        true
84✔
173
      when ::Array
18✔
174
        !object.empty?
30✔
175
      when ::Hash
18✔
176
        !object.empty?
30✔
177
      when ::Symbol
342✔
178
        !object.empty?
570✔
179
      when ::String
2,087✔
180
        !(
1,394✔
181
          object.empty? ||
2,786✔
182
            begin
183
              ACTIVE_SUPPORT_CORE_EXT_BLANK_RE.match?(object)
3,234✔
184
            rescue ::Encoding::CompatibilityError
185
              ACTIVE_SUPPORT_CORE_EXT_ENCODED_BLANKS[object.encoding].match?(object)
112✔
186
            end
229✔
187
        )
229✔
188
      when ::Numeric
144✔
189
        true
224✔
190
      when ::Time
9✔
191
        true
14✔
192
      when ::Object
99✔
193
        !(
55✔
194
          object.respond_to?(:empty?) ? !!object.empty? : false
158✔
195
        )
11✔
196
      else
×
197
        object.present?
×
198
      end
199
    end
200

201
    ##
202
    # Returns `false` when `object` is `false` or `nil`.
203
    # Returns `true` for all the other cases.
204
    # Just like Ruby does in the control expressions.
205
    #
206
    # @api public
207
    # @param object [Object] Can be any type.
208
    # @return [Boolean]
209
    #
210
    # @note If performance is a concern, prefer to use `!!` directly.
211
    # @see https://docs.ruby-lang.org/en/3.4/syntax/control_expressions_rdoc.html
212
    #
213
    def to_ruby_bool(object)
14✔
214
      !!object
5,410✔
215
    end
216

217
    ##
218
    # A handy alias for `to_ruby_bool`.
219
    #
220
    # @api public
221
    # @return [Boolean]
222
    #
223
    alias_method :to_bool, :to_ruby_bool
14✔
224

225
    if ::Gem::Version.create(::RUBY_VERSION) >= ::Gem::Version.create("2.6")
14✔
226
      ##
227
      # Converts `object` to a boolean by the following logic:
228
      # - Converts `object` to a string by the `#to_s` method and checks whether it is one of `["t", "T", "true", "True", "TRUE", "on", "On", "ON", "y", "Y", "yes", "Yes", "YES"]`.
229
      # - If yes, returns `true`, otherwise it converts `object` to an integer by `Kernel.Integer` and checks whether it is greater than zero.
230
      # - If yes, returns `true`, otherwise returns `false`.
231
      #
232
      # @api public
233
      # @param object [Object] Can be any type.
234
      # @return [Boolean]
235
      #
8✔
236
      def to_env_bool(object)
12✔
237
        string = object.to_s
3,822✔
238

239
        return false if string.empty?
3,810✔
240

241
        return true if TO_ENV_BOOL_TRUE_VALUES.include?(string)
3,786✔
242

243
        integer = ::Kernel.Integer(string, exception: false)
3,414✔
244

245
        return false unless integer
3,334✔
246

247
        integer > 0
144✔
248
      rescue ::Encoding::CompatibilityError
249
        false
80✔
250
      end
251
    else
252
      ##
253
      # Converts `object` to a boolean by the following logic:
254
      # - Converts `object` to a string by the `#to_s` method and checks whether it is one of `["t", "T", "true", "True", "TRUE", "on", "On", "ON", "y", "Y", "yes", "Yes", "YES"]`.
255
      # - If yes, returns `true`, otherwise it converts `object` to an integer by `Kernel.Integer` and checks whether it is greater than zero.
256
      # - If yes, returns `true`, otherwise returns `false`.
257
      #
258
      # @api public
259
      # @param object [Object] Can be any type.
260
      # @return [Boolean]
261
      #
262
      # rubocop:disable Lint/SuppressedExceptionInNumberConversion
1!
263
      def to_env_bool(object)
2✔
264
        string = object.to_s
630✔
265

266
        return false if string.empty?
628!
267

268
        return true if TO_ENV_BOOL_TRUE_VALUES.include?(string)
624!
269

270
        integer =
562✔
271
          begin
272
            ::Kernel.Integer(string)
562✔
273
          rescue
274
            nil
538✔
275
          end
276

277
        return false unless integer
562!
278

279
        integer > 0
24✔
280
      rescue ::Encoding::CompatibilityError
281
        false
×
282
      end
283
      # rubocop:enable Lint/SuppressedExceptionInNumberConversion
284
    end
285

286
    ##
287
    # Converts `object` to boolean (or `nil`) using exactly the same logic as `ActiveModel::Type::Boolean.new.cast(object)` does.
288
    #
289
    # @api public
290
    # @param object [Object] Can be any type.
291
    # @return [Boolean, nil]
292
    #
293
    # @note If performance is a concern, prefer to load Rails (or just `activemodel`) and use `ActiveModel::Type::Boolean.new.cast(object)` directly.
294
    # @see https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
295
    # @see https://github.com/rails/rails/blob/v8.0.2/activemodel/lib/active_model/type/boolean.rb#L39
296
    #
297
    def to_active_model_boolean_type(object)
14✔
298
      (object == "") ? nil : !TO_ACTIVE_MODEL_BOOLEAN_TYPE_FALSE_VALUES.include?(object)
4,770✔
299
    end
300

301
    ##
302
    # Converts `object` to `1` or `0`.
303
    # Uses `to_ruby_bool` method under the hood.
304
    # Accepts optional `:by` keyword to rely on a different method.
305
    # Accepts optional `:unknown` keyword that specify what to return when `object` is `nil`.
306
    #
307
    # @api public
308
    # @param object [Object] Can be any type.
309
    # @param by [Symbol, String].
310
    # @param unknown [Object] Can be any type.
311
    # @return [Integer]
312
    #
313
    # @example Call without the `:by` keyword.
314
    #   SemanticBoolean.to_one_or_zero("")
315
    #   # => 1
316
    #
317
    # @example Call with the `:by` keyword.
318
    #   SemanticBoolean.to_one_or_zero("", by: :present?)
319
    #   # => 0
320
    #
321
    # @example Call with the `:unknown` keyword.
322
    #   SemanticBoolean.to_one_or_zero(nil, unknown: 127)
323
    #   # => 127
324
    #
325
    def to_one_or_zero(object, by: :to_ruby_bool, unknown: false)
14✔
326
      return unknown if object.nil?
238✔
327

328
      public_send(by, object) ? 1 : 0
224✔
329
    end
330

331
    ##
332
    # Converts `object` to `"y"` or `"n"`.
333
    # Uses `to_ruby_bool` method under the hood.
334
    # Accepts optional `:by` keyword to rely on a different method.
335
    # Accepts optional `:unknown` keyword that specify what to return when `object` is `nil`.
336
    #
337
    # @api public
338
    # @param object [Object] Can be any type.
339
    # @param by [Symbol, String].
340
    # @param unknown [Object] Can be any type.
341
    # @return [String]
342
    #
343
    # @example Call without the `:by` keyword.
344
    #   SemanticBoolean.to_y_or_n("n")
345
    #   # => "y"
346
    #
347
    # @example Call with the `:by` keyword.
348
    #   SemanticBoolean.to_y_or_n("n", by: :to_env_bool)
349
    #   # => "n"
350
    #
351
    # @example Call with the `:unknown` keyword.
352
    #   SemanticBoolean.to_y_or_n(nil, unknown: "")
353
    #   # => ""
354
    #
355
    def to_y_or_n(object, by: :to_ruby_bool, unknown: false)
14✔
356
      return unknown if object.nil?
238✔
357

358
      public_send(by, object) ? "y" : "n"
224✔
359
    end
360

361
    ##
362
    # Converts `object` to `"yes"` or `"no"`.
363
    # Uses `to_ruby_bool` method under the hood.
364
    # Accepts optional `:by` keyword to rely on a different method.
365
    # Accepts optional `:unknown` keyword that specify what to return when `object` is `nil`.
366
    #
367
    # @api public
368
    # @param object [Object] Can be any type.
369
    # @param by [Symbol, String].
370
    # @param unknown [Object] Can be any type.
371
    # @return [String]
372
    #
373
    # @example Call without the `:by` keyword.
374
    #   SemanticBoolean.to_yes_or_no([])
375
    #   # => "yes"
376
    #
377
    # @example Call with the `:by` keyword.
378
    #   SemanticBoolean.to_yes_or_no([], by: :present?)
379
    #   # => "no"
380
    #
381
    # @example Call with the `:unknown` keyword.
382
    #   SemanticBoolean.to_yes_or_no(nil, unknown: "unknown")
383
    #   # => "unknown"
384
    #
385
    def to_yes_or_no(object, by: :to_ruby_bool, unknown: false)
14✔
386
      return unknown if object.nil?
238✔
387

388
      public_send(by, object) ? "yes" : "no"
224✔
389
    end
390

391
    ##
392
    # Converts `object` to `"on"` or `"off"`.
393
    # Uses `to_ruby_bool` method under the hood.
394
    # Accepts optional `:by` keyword to rely on a different method.
395
    # Accepts optional `:unknown` keyword that specify what to return when `object` is `nil`.
396
    #
397
    # @api public
398
    # @param object [Object] Can be any type.
399
    # @param by [Symbol, String].
400
    # @param unknown [Object] Can be any type.
401
    # @return [String]
402
    #
403
    # @example Call without the `:by` keyword.
404
    #   SemanticBoolean.to_on_or_off(false)
405
    #   # => "off"
406
    #
407
    # @example Call with the `:by` keyword.
408
    #   SemanticBoolean.to_yes_or_no(false, by: :blank?)
409
    #   # => "on"
410
    #
411
    # @example Call with the `:unknown` keyword.
412
    #   SemanticBoolean.to_on_or_off(nil, unknown: "unavailable")
413
    #   # => "unavailable"
414
    #
415
    def to_on_or_off(object, by: :to_ruby_bool, unknown: false)
14✔
416
      return unknown if object.nil?
238✔
417

418
      public_send(by, object) ? "on" : "off"
224✔
419
    end
420

421
    ##
422
    # Converts `object` to `true` or `false`.
423
    # Uses `to_ruby_bool` method under the hood.
424
    # Accepts optional `:by` keyword to rely on a different method.
425
    # Accepts optional `:unknown` keyword that specify what to return when `object` is `nil`.
426
    #
427
    # @api public
428
    # @param object [Object] Can be any type.
429
    # @param by [Symbol, String].
430
    # @param unknown [Object] Can be any type.
431
    # @return [String]
432
    #
433
    # @example Call without the `:by` keyword.
434
    #   SemanticBoolean.to_on_or_off(false)
435
    #   # => "off"
436
    #
437
    # @example Call with the `:by` keyword.
438
    #   SemanticBoolean.to_yes_or_no(false, by: :blank?)
439
    #   # => "on"
440
    #
441
    # @example Call with the `:unknown` keyword.
442
    #   SemanticBoolean.to_on_or_off(nil, unknown: "unavailable")
443
    #   # => "unavailable"
444
    #
445
    def to_true_or_false(object, by: :to_ruby_bool, unknown: false)
14✔
446
      return unknown if object.nil?
238✔
447

448
      public_send(by, object) ? true : false
224✔
449
    end
450
  end
451
end
452
# rubocop:enable Lint/BooleanSymbol
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc