Ciąg znaków „prawda” i „fałsz” na wartość logiczną

85

Mam aplikację Rails i używam jQuery do odpytywania mojego widoku wyszukiwania w tle. Istnieją pola q(wyszukiwane hasło) start_date, end_datei internal. To internalpole jest polem wyboru i używam is(:checked)metody do budowania adresu URL, który jest odpytywany:

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

Teraz mój problem tkwi w tym, params[:internal]że istnieje łańcuch zawierający „prawda” lub „fałsz” i muszę go rzutować na wartość logiczną. Oczywiście mogę to zrobić w ten sposób:

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

Ale myślę, że musi istnieć bardziej Rubinowy sposób rozwiązania tego problemu! Czy nie ma ...?

davidb
źródło

Odpowiedzi:

133

O ile wiem, nie ma wbudowanej metody rzutowania łańcuchów na wartości logiczne, ale jeśli twoje łańcuchy składają się tylko z 'true'i 'false'możesz skrócić swoją metodę do następującej:

def to_boolean(str)
  str == 'true'
end
cvshepherd
źródło
8
tylko mała modyfikacja str == 'true' || str = '1'
AMTourky
30
być może str.downcase == 'true' dla kompletności
JEMaddux
@AMTourky nie powinno być str == 'true' || str == '1' z dwoma "=="?
Pascal
@Lowryder Yes! jeśli używasz domyślnego pola wyboru.
7urkm3n
49

ActiveRecord zapewnia przejrzysty sposób na zrobienie tego.

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

ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES ma wszystkie oczywiste reprezentacje wartości True jako łańcuchy.

Satya Kalluri
źródło
16
Jeszcze prostsze, po prostu użyj ActiveRecord::ConnectionAdapters::Column.value_to_boolean(string)(źródło) apidock.com/rails/v3.0.9/ActiveRecord/ConnectionAdapters/Column/ ...
Mike Atlas
Tak, w najnowszych wersjach!
Satya Kalluri
6
ActiveRecord::Type::Boolean.new.type_cast_from_user("true")=> true ActiveRecord::Type::Boolean.new.type_cast_from_user("T")=> true
AlexChaffee
Lista wartości fałszywych została przeniesiona do ActiveModel :: Type :: Boolean w Rails 5
divideByZero
ActiveModel::Type::Booleanwydaje się o wiele bardziej odpowiednią ścieżką - chociaż ActiveRecord::ConnectionAdapters::Column::TRUE_VALUESzawierały „prawdziwe” wartości, można by argumentować, że jest to przypadek i że wartości, które powinny być uznane za prawdziwe w tym konkretnym przypadku użycia, ale nie mogą być uwzględnione. Z drugiej strony ActiveModel::Type::Booleanjest najwyraźniej zaprojektowany do użytku w sposób ogólny.
Lyndsy Simon
24

Uwaga dotycząca bezpieczeństwa

Zwróć uwagę, że ta odpowiedź w czystej postaci jest odpowiednia tylko dla innego przypadku użycia wymienionego poniżej, a nie tego w pytaniu. Chociaż w większości naprawiono, pojawiło się wiele luk w zabezpieczeniach związanych z YAML, które były spowodowane ładowaniem danych wejściowych użytkownika jako YAML.


Sztuczka, której używam do konwersji ciągów znaków na bools, to na YAML.loadprzykład:

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

YAML bool akceptuje całkiem sporo prawdziwych / fałszywych ciągów:

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

Inny przypadek użycia

Załóżmy, że masz taki fragment kodu konfiguracyjnego:

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

A w linii poleceń:

$ export ETC_SOMETHING=false

Ponieważ ENVzmienne są ciągami znaków znajdującymi się w kodzie, config.etc.somethingwartość byłaby łańcuchem "false"i byłaby niepoprawnie obliczana do true. Ale jeśli podoba ci się to:

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

wszystko byłoby w porządku. Jest to również zgodne z ładowaniem konfiguracji z plików .yml.

Halil Özgür
źródło
1
To dobrze, jeśli przekazany ciąg jest pod twoją kontrolą. W przypadku tego pytania podane wartości pochodzą z przeglądarki użytkownika i jako takie należy je uznać za niebezpieczne. YAML pozwala na serializację / deserializację dowolnego obiektu Rubiego i jest to potencjalnie niebezpieczne.
Zdarzyło
1
@Teoulas, całkowicie się z tobą zgadzam. W rzeczywistości dodaję powiadomienie, aby ludzie nie używali tego w niezabezpieczony sposób.
Halil Özgür
16

Nie ma żadnego wbudowanego sposobu, aby sobie z tym poradzić (chociaż pakiet akcji może mieć do tego pomocnika). Radziłbym coś takiego

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

Co również działa, to użycie 0 i wartości różnej od 0 zamiast fałszywych / prawdziwych literałów:

def to_boolean(s)
  !s.to_i.zero?
end
Marcel Jackwerth
źródło
3
nie potrzebujesz zabezpieczenia „s i ...”, jeśli używasz „!! (s = ~ / regex_here /)”, ponieważ „nil = ~ / cokolwiek /” zwraca zero.
Pavling,
Ach, rzeczywiście. Dodałem go, ale zachowałem również stary, ponieważ myślę, że .matchjest trochę łatwiejszy do odczytania.
Marcel Jackwerth,
7

ActiveRecord::Type::Boolean.new.type_cast_from_userrobi to zgodnie z wewnętrznym mapowaniem Railsów ConnectionAdapters::Column::TRUE_VALUESi 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

Możesz więc stworzyć własną to_b(lub to_boollub to_boolean) metodę w inicjatorze takim jak ten:

class String
  def to_b
    ActiveRecord::Type::Boolean.new.type_cast_from_user(self)
  end
end
AlexChaffee
źródło
2
To jest ActiveRecord :: Type :: Boolean.new.cast (value) w Rails 5 (patrz CWitty poniżej)
Dave Burt
6

Możesz użyć gem wannabe_bool. https://github.com/prodis/wannabe_bool

Ten klejnot implementuje #to_bmetodę dla klas String, Integer, Symbol i NilClass.

params[:internal].to_b
Prodis
źródło
6

W Rails 5 możesz użyć ActiveRecord::Type::Boolean.new.cast(value)do rzutowania go na wartość logiczną.

CWitty
źródło
1
uważaj jednak, ActiveRecord :: Type :: Boolean.new.cast ("42") zwraca true
divideByZero
3

Nie sądzę, żeby coś takiego było wbudowane w Ruby. Możesz ponownie otworzyć klasę String i dodać tam metodę to_bool:

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

Następnie możesz go użyć w dowolnym miejscu w swoim projekcie, na przykład: params[:internal].to_bool

socha23
źródło
2
Zdecydowanie nie chciałbym mieć to_boolpowrotu funkcji nil; to wydaje się złe. Inne funkcje konwersji tego nie robią: "a".to_izwraca 0, nienil
Krease,
3

Może str.to_s.downcase == 'true'dla kompletności. Wtedy nic nie może się zawiesić, nawet jeśli strwynosi zero lub 0.

user1839842
źródło
2

Patrząc na kod źródłowy Virtusa , może zrobiłbym coś takiego:

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
źródło
1

Możesz rozważyć dołączenie internaldo swojego adresu URL tylko wtedy, gdy jest prawdziwe, a wtedy, jeśli pole wyboru nie jest zaznaczone i nie zostanie dołączone, params[:internal]będzie to oznaczać nilwartość false w Rubim.

Nie jestem zaznajomiony z konkretnym jQuery, którego używasz, ale czy istnieje bardziej przejrzysty sposób wywoływania tego, co chcesz, niż ręczne tworzenie ciągu adresu URL? Czy spojrzałeś na $geti $ajax?

Russell
źródło
1

Możesz dodać do klasy String, aby uzyskać metodę to_boolean. Następnie możesz zrobić „true ”.to_boolean lub„ 1 ”.to_boolean

class String
  def to_boolean
    self == 'true' || self == '1'
  end
end
Josh Cavin
źródło
-5

Dziwię się, że nikt nie opublikował tego prostego rozwiązania. To znaczy, jeśli twoje łańcuchy będą „prawdziwe” lub „fałszywe”.

def to_boolean(str)
    eval(str)
end
povess
źródło
4
To dlatego, że to rozwiązanie jest katastrofą bezpieczeństwa. : D
davidb
1
Problem z tym rozwiązaniem polega na wprowadzaniu danych przez użytkownika - jeśli ktoś wpisze to_boolean("ActiveRecord::Base.connection.execute('DROP TABLE *')"), zniszczy to twoją bazę danych (i zwróci true!). Baw się dobrze: D
Ben Aubin,
Słuszne uwagi. Nie myślałem o kontekście. Myślałem o najmniejszej liczbie znaków do zaimplementowania. :)
povess
Łatwa naprawa wspomnianej luki: bool = nil; bool = eval(str) if ["true", "false"].include?(str)pomyślałem, że powinienem dodać w celu wyjaśnienia.
Fernando Cordeiro