87

I have a Rails application and I'm using jQuery to query my search view in the background. There are fields q (search term), start_date, end_date and internal. The internal field is a checkbox and I'm using the is(:checked) method to build the url that is queried:

$.getScript(document.URL + "?q=" + $("#search_q").val() + "&start_date=" + $("#search_start_date").val() + "&end_date=" + $("#search_end_date").val() + "&internal=" + $("#search_internal").is(':checked'));

Now my problem is in params[:internal] because there is a string either containing "true" or "false" and I need to cast it to boolean. Of course I can do it like this:

def to_boolean(str)
     return true if str=="true"
     return false if str=="false"
     return nil
end

But I think there must be a more Ruby'ish way to deal with this problem! Isn't there...?

cweston
  • 11,297
  • 19
  • 82
  • 107
davidb
  • 8,884
  • 4
  • 36
  • 72

13 Answers13

136

As far as i know there is no built in way of casting strings to booleans, but if your strings only consist of 'true' and 'false' you could shorten your method to the following:

def to_boolean(str)
  str == 'true'
end
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
cvshepherd
  • 3,947
  • 3
  • 20
  • 14
49

ActiveRecord provides a clean way of doing this.

def is_true?(string)
  ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(string)
end

ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES has all of the obvious representations of True values as strings.

aaron-coding
  • 2,571
  • 1
  • 23
  • 31
Satya Kalluri
  • 5,148
  • 4
  • 28
  • 37
  • 16
    Even simpler, just use `ActiveRecord::ConnectionAdapters::Column.value_to_boolean(string)` (source) http://apidock.com/rails/v3.0.9/ActiveRecord/ConnectionAdapters/Column/value_to_boolean/class – Mike Atlas Feb 06 '14 at 17:35
  • Yes, in the latest versions! – Satya Kalluri Jul 07 '14 at 08:25
  • 6
    `ActiveRecord::Type::Boolean.new.type_cast_from_user("true")` => true `ActiveRecord::Type::Boolean.new.type_cast_from_user("T")` => true – AlexChaffee Apr 30 '15 at 20:07
  • False value list has been moved to ActiveModel::Type::Boolean in Rails 5 – divideByZero Dec 16 '16 at 13:19
  • `ActiveModel::Type::Boolean` seems like a much more suitable path - while `ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES` contained "truthy" values, one could argue that it's only incidentally the case, and that values that should be considered truthy for that specific use case but not others could be included. On the other hand, `ActiveModel::Type::Boolean` is apparently designed to be used in a generic way. – Lyndsy Simon Feb 15 '17 at 17:12
24

Security Notice

Note that this answer in its bare form is only appropriate for the other use case listed below rather than the one in the question. While mostly fixed, there have been numerous YAML related security vulnerabilities which were caused by loading user input as YAML.


A trick I use for converting strings to bools is YAML.load, e.g.:

YAML.load(var) # -> true/false if it's one of the below

YAML bool accepts quite a lot of truthy/falsy strings:

y|Y|yes|Yes|YES|n|N|no|No|NO
|true|True|TRUE|false|False|FALSE
|on|On|ON|off|Off|OFF

Another use case

Assume that you have a piece of config code like this:

config.etc.something = ENV['ETC_SOMETHING']

And in command line:

$ export ETC_SOMETHING=false

Now since ENV vars are strings once inside code, config.etc.something's value would be the string "false" and it would incorrectly evaluate to true. But if you do like this:

config.etc.something = YAML.load(ENV['ETC_SOMETHING'])

it would be all okay. This is compatible with loading configs from .yml files as well.

Halil Özgür
  • 15,731
  • 6
  • 49
  • 56
  • 2
    That's good if the passed string is under your control. In this question's case, the provided values come from the user's browser and as such, they should be considered unsafe. YAML allows you to serialize/deserialize any Ruby object and this is potentially dangerous. There have been numerous incidents: https://www.google.com/webhp?q=rails+yaml+vulnerability – Teoulas Aug 19 '15 at 11:01
  • 1
    @Teoulas, I completely agree with you. In fact, I'm adding a notice so people don't use this in an insecure way. – Halil Özgür Aug 19 '15 at 14:49
16

There isn't any built-in way to handle this (although actionpack might have a helper for that). I would advise something like this

def to_boolean(s)
  s and !!s.match(/^(true|t|yes|y|1)$/i)
end

# or (as Pavling pointed out)

def to_boolean(s)
  !!(s =~ /^(true|t|yes|y|1)$/i)
end

What works as well is to use 0 and non-0 instead of false/true literals:

def to_boolean(s)
  !s.to_i.zero?
end
Marcel Jackwerth
  • 53,948
  • 9
  • 74
  • 88
8

ActiveRecord::Type::Boolean.new.type_cast_from_user does this according to Rails' internal mappings ConnectionAdapters::Column::TRUE_VALUES and ConnectionAdapters::Column::FALSE_VALUES:

[3] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("true")
=> true
[4] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("false")
=> false
[5] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("T")
=> true
[6] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("F")
=> false
[7] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("yes")
DEPRECATION WARNING: You attempted to assign a value which is not explicitly `true` or `false` ("yes") to a boolean column. Currently this value casts to `false`. This will change to match Ruby's semantics, and will cast to `true` in Rails 5. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to `false`. (called from <main> at (pry):7)
=> false
[8] pry(main)> ActiveRecord::Type::Boolean.new.type_cast_from_user("no")
DEPRECATION WARNING: You attempted to assign a value which is not explicitly `true` or `false` ("no") to a boolean column. Currently this value casts to `false`. This will change to match Ruby's semantics, and will cast to `true` in Rails 5. If you would like to maintain the current behavior, you should explicitly handle the values you would like cast to `false`. (called from <main> at (pry):8)
=> false

So you could make your own to_b (or to_bool or to_boolean) method in an initializer like this:

class String
  def to_b
    ActiveRecord::Type::Boolean.new.type_cast_from_user(self)
  end
end
AlexChaffee
  • 8,092
  • 2
  • 49
  • 55
7

In Rails 5 you can use ActiveRecord::Type::Boolean.new.cast(value) to cast it to a boolean.

CWitty
  • 4,488
  • 3
  • 23
  • 40
6

You can use wannabe_bool gem. https://github.com/prodis/wannabe_bool

This gem implements a #to_b method for String, Integer, Symbol and NilClass classes.

params[:internal].to_b
Prodis
  • 506
  • 6
  • 8
5

Perhaps str.to_s.downcase == 'true' for completeness. Then nothing can crash even if str is nil or 0.

twernt
  • 20,271
  • 5
  • 32
  • 41
user1839842
  • 83
  • 1
  • 5
3

I don't think anything like that is built-in in Ruby. You can reopen String class and add to_bool method there:

class String
    def to_bool
        return true if self=="true"
        return false if self=="false"
        return nil
    end
end

Then you can use it anywhere in your project, like this: params[:internal].to_bool

socha23
  • 10,171
  • 2
  • 28
  • 25
  • 2
    I definitely wouldn't want to have a `to_bool` function return `nil`; that seems wrong. Other conversion functions don't do this: `"a".to_i` returns `0`, not `nil` – Krease Jul 08 '14 at 19:58
2

Looking at the source code of Virtus, I'd maybe do something like this:

def to_boolean(s)
  map = Hash[%w[true yes 1].product([true]) + %w[false no 0].product([false])]
  map[s.to_s.downcase]
end
d11wtq
  • 34,788
  • 19
  • 120
  • 195
1

You could add to the String class to have the method of to_boolean. Then you could do 'true'.to_boolean or '1'.to_boolean

class String
  def to_boolean
    self == 'true' || self == '1'
  end
end
Josh Cavin
  • 61
  • 1
  • 3
1

You could consider only appending internal to your url if it is true, then if the checkbox isn't checked and you don't append it params[:internal] would be nil, which evaluates to false in Ruby.

I'm not that familiar with the specific jQuery you're using, but is there a cleaner way to call what you want than manually building a URL string? Have you had a look at $get and $ajax?

Russell
  • 12,261
  • 4
  • 52
  • 75
-5

I'm surprised no one posted this simple solution. That is if your strings are going to be "true" or "false".

def to_boolean(str)
    eval(str)
end
povess
  • 1
  • 1
  • 5
    Thats because This solution is a security Desaster. :D – davidb Nov 11 '15 at 18:04
  • 1
    The problem with this solution is user input - if someone types `to_boolean("ActiveRecord::Base.connection.execute('DROP TABLE *')")` , it'll destroy your database (and return true!). Have fun :D – Ben Aubin Dec 11 '15 at 20:56
  • Good points. I wasn't thinking of the context. I was thinking the least amount of characters to implement. :) – povess Jan 02 '16 at 18:24
  • An easy fix for said vulnerability: `bool = nil; bool = eval(str) if ["true", "false"].include?(str)` Just thought I should add for the sake of clarifications. – Fernando Cordeiro Jul 19 '20 at 23:53