Як подати логічний параметр у Rails?


75

Я подаю параметр show_allзі значенням true. Це значення не пов’язане з моделлю.

Мій контролер призначає цей параметр змінній екземпляра:

@show_all = params[:show_all]

Однак, @show_all.is_a? Stringі if @show_all == trueзавжди не вдається.

Які значення Rails аналізує як логічні значення? Як я явно можу вказати, що мій параметр є логічним значенням, а не рядком?


У рубіні немає такого поняття, як логічні значення, TrueClassі лишеFalseClass
NullUserException

4
Rails все ще автоматично визначає це, коли параметр пов'язаний з моделлю - якщо тип стовпця в базі даних логічний, параметр розглядається як TrueClassабо FalseClass. Будь-яка ідея, як я можу це зробити красиво?
nfm

можливий дублікат Як перевірити, чи є параметр істинним чи хибним? через "Чи є кращий спосіб зробити це, якщо операція if / else заснована на параметрі, який є істинним чи хибним?"
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Хороше рішення цього питання також може бути хорошим рішенням цього.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Відповіді:


70

Я хотів прокоментувати задумливу відповідь, але оскільки я поки що не можу цього зробити, я опублікую це як відповідь.

Якщо ви використовуєте

@show_all = params[:show_all] == "1"

тоді ви можете відмовитись, ? true : falseтому що params[:show_all] == "1"сам вираз буде оцінюватися як true або false і, отже, тернарний оператор не потрібен.


для наступних посилань це найчистіша відповідь. Технічно, булеве значення може бути передане як "true" або "false", але передавати число краще просто тому, що це запобігає поверненню false помилок, таких як "true" == "True". Або 1, або 0.
sybohy

100

ОНОВЛЕННЯ: Rails 5:

ActiveRecord::Type::Boolean.new.deserialize('0')

ОНОВЛЕННЯ: Rails 4.2 має загальнодоступний API для цього:

ActiveRecord::Type::Boolean.new.type_cast_from_user("0") # false

ПОПЕРЕДНЯ ВІДПОВІДЬ:

ActiveRecord підтримує список подань для true / false на https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/column.rb

2.0.0-p247 :005 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("ON")
2.0.0-p247 :006 > ActiveRecord::ConnectionAdapters::Column.value_to_boolean("F")

Це не є частиною загальнодоступного API Rails, тому я перетворив його на допоміжний метод:

class ApplicationController < ActionController::Base
  private

  def parse_boolean(value)
    ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
  end
end

і додав базовий тест:

class ApplicationControllerTest < ActionController::TestCase
  test "parses boolean params" do
    refute ApplicationController.new.send(:parse_boolean, "OFF")
    assert ApplicationController.new.send(:parse_boolean, "T")
  end
end

3
однак слід зазначити, що ActiveRecord::Type::Boolean.new.type_cast_from_user("0")повернеться, falseяк ви вже згадали, але в Rails 5 воно почне повертати true: DEPRECATION ПОПЕРЕДЖЕННЯ: Ви намагалися привласнити значення, яке не є явним trueабо falseлогічним стовпцем. В даний час це значення має false. Це зміниться, щоб відповідати семантиці Ruby, і буде перетворено на trueRails 5. Якщо ви хочете зберегти поточну поведінку, вам слід чітко обробити значення, до яких ви хочете передати false.
Джон Сміт

1
ActiveRecord::Type::Booleanце просто псевдонім ActiveModel::Type::Boolean. Можливо, трохи краще використовувати оригінальний клас.
Тім Дорр

22

Це запитання досить давнє, але оскільки я пару разів стикався з цим питанням і мені не сподобалось жодне із запропонованих рішень, я сам зламав щось, що дозволяє використовувати декілька рядків для істинних, таких як 'так', 'на' , 't' і протилежне для false.

Мавпа виправляє клас String і додає метод, щоб перетворити їх на логічний, і помістіть цей файл, /config/initializersяк запропоновано тут: Виправлення мавп у Rails 3

class String
  def to_bool
    return true if ['true', '1', 'yes', 'on', 't'].include? self
    return false if ['false', '0', 'no', 'off', 'f'].include? self
    return nil
  end
end

Зверніть увагу, що якщо значення не є жодним з дійсних чи то для true, ні для false, то воно повертає нуль. Шукати не однаково ?paid=false(повернути всі записи, що не оплачені), ніж ?paid=(я не вказую, чи потрібно це платити, чи ні - тому відкиньте це).

Тоді, слідуючи цьому прикладу, логіка у вашому контролері буде виглядати так:

Something.where(:paid => params[:paid].to_bool) unless params[:paid].try(:to_bool).nil?

Це досить акуратно і допомагає підтримувати контролери / моделі в чистоті.


2
Відмінне рішення, чисте, акуратне та додає справді корисну функціональність класу String. Дуже мило.
Шон Камерон

19
Вітаємо, тепер у вас є String#to_boolте, що може повернутися nil. Будь ласка, медитуйте над цим деякий час, а потім або перейменуйте метод на щось подібне, String#maybe_bool?або до цього ArgumentError.
Нікос,

2
Це можна було б спростити ще більше, оскількиdef to_bool; ['true', '1', 'yes', 'on', 't'].include? self; end
lobati

5
Кожного разу, коли ви викликаєте цей метод, ви генеруєте принаймні один масив. Перемістіть ці масиви в застиглі константи. TRUE_VALUES = ['true'.freeze, '1'.freeze, 'yes'.freeze, 'on'.freeze, 't'.freeze]. Це a) економить пам'ять і b) запобігає змінам рядків під час виконання.
Кріс,

@Nicos, хоча я погоджуюся з вашим коментарем, майте на увазі, що, принаймні в Rails 3.2 (не перевірено в інших версіях), ActiveRecord::ConnectionAdapters::Column.value_to_boolean('')повернеться nil.
Олександр

16
@show_all = params[:show_all] == "1" ? true : false

Це має добре працювати, якщо ви передаєте значення з прапорця - відсутній ключ у хеші генерує нуль, який в умовному виражається значенням false.

РЕДАГУВАТИ

Як зазначалося тут , тернарний оператор не є необхідним, тому це може бути просто:

@show_all = params[:show_all] == "1"


4

Ви можете змінити своє твердження про рівність на:

@show_all == "true"

Якщо ви хочете, щоб це було логічне значення, ви можете створити метод для класу рядків для перетворення рядка в логічний.


3

Я думаю, що найпростішим рішенням є тестування "булевих" параметрів щодо їх подання String.

@show_all = params[:show_all]
if @show_all.to_s == "true"
   # do stuff
end

Незалежно від того, чи Rails передає параметр як рядок "true" або "false", або фактичний TrueClass або FalseClass, цей тест завжди буде працювати.


1

Інший підхід полягає у передачі лише ключа без значення. Хоча використання ActiveRecord::Type::Boolean.new.type_cast_from_user(value)досить акуратне, може бути ситуація, коли присвоєння значення ключу param надмірне.

Розглянемо наступне: За замовчуванням у моєму індексному поданні я хочу показувати лише колекцію продуктів із обсягом (наприклад, ті, що є в наявності). Тобто, якщо я хочу повернути всі продукти, я можу надіслати myapp.com/products?show_all=trueі show_allввести параметр для логічного значення.

Однак протилежний варіант - myapp.com/products?show_all=falseпросто немає сенсу, оскільки він поверне ту саму колекцію товарів, myapp.com/productsщо і повернув би.

Альтернатива:

якщо я хочу повернути всю колекцію, яка не потрапила в список, тоді я надсилаю myapp.com/products?allі в моєму контролері визначаю

private

def show_all?
  params.key?(:all)
end

Якщо ключ присутній у params, то незалежно від його значення, я знатиму, що мені потрібно повернути всі продукти, не потрібно вводити значення typecast.


Я вирішив використовувати цей підхід для свого випадку використання. Раніше я неоднозначно ставився до цього, але якщо реального випадку використання хибного значення немає, то має сенс зробити це таким чином!
RSmithlal

1

Ви можете додати до своєї моделі наступне:

def show_all= value
  @show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end

1

Ви могли б просто зробити

@show_all = params[:show_all].downcase == 'true'

0

Ви можете перетворити всі свої логічні параметри на справжні логічні значення, як це:

%w(show_all, show_featured).each do |bool_param|
  params[bool_param.to_sym] = params[bool_param.to_sym] == "true"
end

У цьому рішенні нульові параметри стануть хибними.


0

Варто зазначити, що якщо ви передаєте значення ActiveModel у Rails> 5.2, простішим рішенням є використання attribute,

class Model
  include ActiveModel::Attributes

  attribute :show_all, :boolean
end

Model.new(show_all: '0').show_all # => false

Як видно тут .


До 5.2 я використовую:

class Model
  include ActiveModel::Attributes

  attribute_reader :show_all

  def show_all=(value)
    @show_all = ActiveModel::Type::Boolean.new.cast(value)
  end
end

Model.new(show_all: '0').show_all # => false
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.