String “true” e “false” para booleano

86

Eu tenho um aplicativo Rails e estou usando jQuery para consultar minha visualização de pesquisa em segundo plano. Existem campos q(termo de pesquisa) start_date, end_datee internal. O internalcampo é uma caixa de seleção e estou usando o is(:checked)método para construir o url que é consultado:

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

Agora meu problema é params[:internal]porque há uma string contendo "true" ou "false" e eu preciso convertê-la em booleano. Claro que posso fazer assim:

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

Mas eu acho que deve haver uma maneira mais Ruby de lidar com esse problema! Não há ...?

David
fonte

Respostas:

134

Pelo que eu sei, não existe uma maneira embutida de converter strings para booleanos, mas se suas strings consistirem apenas em 'true'e 'false'você puder encurtar seu método para o seguinte:

def to_boolean(str)
  str == 'true'
end
cvshepherd
fonte
8
apenas uma pequena modificação str == 'true' || str = '1'
AMTourky
30
talvez str.downcase == 'true' para integridade
JEMaddux
@AMTourky não deveria ser str == 'true' || str == '1' com dois "=="?
Pascal
@Lowryder Sim! se estiver usando a caixa de seleção padrão.
7urkm3n
49

ActiveRecord fornece uma maneira limpa de fazer isso.

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

ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES tem todas as representações óbvias de valores True como strings.

Satya Kalluri
fonte
16
Ainda mais simples, basta usar ActiveRecord::ConnectionAdapters::Column.value_to_boolean(string)(fonte) apidock.com/rails/v3.0.9/ActiveRecord/ConnectionAdapters/Column/…
Mike Atlas
Sim, nas últimas versões!
Satya Kalluri
6
ActiveRecord::Type::Boolean.new.type_cast_from_user("true")=> true ActiveRecord::Type::Boolean.new.type_cast_from_user("T")=> true
AlexChaffee
A lista de valores falsos foi movida para ActiveModel :: Type :: Boolean no Rails 5
divideByZero
ActiveModel::Type::Booleanparece um caminho muito mais adequado - embora ActiveRecord::ConnectionAdapters::Column::TRUE_VALUEScontenha valores "verdadeiros", pode-se argumentar que esse é o caso apenas incidentalmente e que valores que devem ser considerados verdadeiros para aquele caso de uso específico, mas não outros, podem ser incluídos. Por outro lado, ActiveModel::Type::Booleanaparentemente foi projetado para ser usado de forma genérica.
Lyndsy Simon de
24

Aviso de Segurança

Observe que esta resposta em sua forma simples é apropriada apenas para o outro caso de uso listado abaixo, e não para aquele na pergunta. Embora a maioria tenha sido corrigida, houve inúmeras vulnerabilidades de segurança relacionadas ao YAML, causadas pelo carregamento da entrada do usuário como YAML.


Um truque que uso para converter strings em bools é YAML.load, por exemplo:

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

O bool YAML aceita muitas strings verdadeiras / falsas:

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

Outro caso de uso

Suponha que você tenha um código de configuração como este:

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

E na linha de comando:

$ export ETC_SOMETHING=false

Agora, como ENVvars são strings uma vez dentro do código, config.etc.somethingo valor de seria a string "false"e seria avaliado incorretamente como true. Mas se você gosta disso:

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

tudo ficaria bem. Isso é compatível com o carregamento de configurações de arquivos .yml também.

Halil Özgür
fonte
1
Isso é bom se a string passada estiver sob seu controle. No caso desta pergunta, os valores fornecidos vêm do navegador do usuário e, como tal, devem ser considerados inseguros. YAML permite serializar / desserializar qualquer objeto Ruby e isso é potencialmente perigoso. Houve vários incidentes: google.com/webhp?q=rails+yaml+vulnerability
Teoulas
1
@Teoulas, concordo totalmente com você. Na verdade, estou adicionando um aviso para que as pessoas não usem isso de maneira insegura.
Halil Özgür
16

Não há nenhuma maneira embutida de lidar com isso (embora o actionpack possa ter um auxiliar para isso). Eu aconselharia algo assim

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

O que funciona bem é usar 0 e não-0 em vez de literais falsos / verdadeiros:

def to_boolean(s)
  !s.to_i.zero?
end
Marcel Jackwerth
fonte
3
você não precisa da proteção "se ..." se usar "!! (s = ~ / regex_here /)" porque "nil = ~ / qualquer coisa /" retorna nulo.
Pavimentação de
Ah, sim. Eu adicionei, mas mantive o antigo também, pois acho que .matché um pouco mais fácil de ler.
Marcel Jackwerth
7

ActiveRecord::Type::Boolean.new.type_cast_from_userfaz isso de acordo com os mapeamentos internos do Rails ConnectionAdapters::Column::TRUE_VALUESe 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

Portanto, você pode fazer seu próprio método to_b(ou to_boolou to_boolean) em um inicializador como este:

class String
  def to_b
    ActiveRecord::Type::Boolean.new.type_cast_from_user(self)
  end
end
AlexChaffee
fonte
2
É ActiveRecord :: Type :: Boolean.new.cast (value) no Rails 5 (veja CWitty abaixo)
Dave Burt
6

Você pode usar wannabe_bool gem. https://github.com/prodis/wannabe_bool

Esta gema implementa um #to_bmétodo para as classes String, Integer, Symbol e NilClass.

params[:internal].to_b
Prodis
fonte
6

No Rails 5 você pode usar ActiveRecord::Type::Boolean.new.cast(value)para convertê-lo em um booleano.

CWitty
fonte
1
esteja ciente, porém, ActiveRecord :: Type :: Boolean.new.cast ("42") retorna true
divideByZero
3

Não acho que algo assim esteja embutido no Ruby. Você pode reabrir a classe String e adicionar o método to_bool lá:

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

Então você pode usá-lo em qualquer lugar em seu projeto, como este: params[:internal].to_bool

socha23
fonte
2
Eu definitivamente não gostaria de ter um to_boolretorno de função nil; isso parece errado. Outras funções de conversão não fazem isso: "a".to_iretorna 0, nãonil
Krease
3

Talvez str.to_s.downcase == 'true'para ser completo. Então, nada pode travar, mesmo se strfor nulo ou 0.

user1839842
fonte
2

Olhando para o código-fonte do Virtus , talvez eu faça algo assim:

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
fonte
1

Você pode considerar apenas anexar internalao seu url se for verdadeiro; então, se a caixa de seleção não estiver marcada e você não anexar, params[:internal]seria nil, o que resulta em falso em Ruby.

Não estou familiarizado com o jQuery específico que você está usando, mas existe uma maneira mais limpa de chamar o que você deseja do que construir manualmente uma string de URL? Você já deu uma olhada em $gete $ajax?

Russell
fonte
1

Você poderia adicionar à classe String para ter o método to_boolean. Então você poderia fazer 'true'.to_boolean ou' 1'.to_boolean

class String
  def to_boolean
    self == 'true' || self == '1'
  end
end
Josh Cavin
fonte
-5

Estou surpreso que ninguém postou esta solução simples. Isso se suas strings forem "verdadeiras" ou "falsas".

def to_boolean(str)
    eval(str)
end
pobre
fonte
4
Isso porque esta solução é um Desaster de segurança. : D
davidb
1
O problema com esta solução é a entrada do usuário - se alguém digitar to_boolean("ActiveRecord::Base.connection.execute('DROP TABLE *')"), isso destruirá seu banco de dados (e retornará verdadeiro!). Divirta-se: D
Ben Aubin
Bons pontos. Eu não estava pensando no contexto. Eu estava pensando na menor quantidade de caracteres para implementar. :)
povess
Uma solução fácil para essa vulnerabilidade: bool = nil; bool = eval(str) if ["true", "false"].include?(str)apenas pensei que deveria acrescentar para esclarecimentos.
Fernando Cordeiro