How do I submit a boolean parameter in Rails?
Asked Answered
S

13

83

I'm submitting a parameter show_all with the value true. This value isn't associated with a model.

My controller is assigning this parameter to an instance variable:

@show_all = params[:show_all]

However, @show_all.is_a? String, and if @show_all == true always fails.

What values does Rails parse as booleans? How can I explicitly specify that my parameter is a boolean, and not a string?

Sturm answered 9/9, 2010 at 3:39 Comment(4)
There is no such thing as booleans in ruby, only TrueClass and FalseClassTroupe
Rails still figures it out automagically when the parameter is associated with a model - if the column type in the database is boolean, it treats the param as a TrueClass or FalseClass. Any idea how I can do this nicely?Sturm
possible duplicate of How to check if a param is true or false? because of "Is there a better way to do that if/else statement based on a param being true or false?"Cai
A good solution to that question might also be a good solution to this one.Cai
A
74

I wanted to comment on zetetic answer but as I can't do that yet I'll post this as an answer.

If you use

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

then you can drop ? true : false because params[:show_all] == "1" statement itself will evaluate to true or false and thus ternary operator is not needed.

Azov answered 9/9, 2010 at 9:10 Comment(1)
for future references, this is the cleanest answer. Technically, the boolean can be passed as "true" or "false" but passing a number is better simply because it prevents simple mistakes such as "true" == "True" from returning false. Either 1, or 0.Crispen
A
110

UPDATE: Rails 5:

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

UPDATE: Rails 4.2 has public API for this:

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

PREVIOUS ANSWER:

ActiveRecord maintains a list of representations for true/false in 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")

This is not part of Rails' public API, so I wrapped it into a helper method:

class ApplicationController < ActionController::Base
  private

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

and added a basic test:

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
Alfred answered 17/10, 2013 at 9:51 Comment(3)
we should note however that ActiveRecord::Type::Boolean.new.type_cast_from_user("0") will return false as you mentioned, but in Rails 5 it will begin returning true: DEPRECATION WARNING: You attempted to assign a value which is not explicitly true or false 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.Numismatics
ActiveRecord::Type::Boolean is just an alias to ActiveModel::Type::Boolean. It's probably a tiny bit better to use the original class.Cavitation
how about ActiveRecord::Type::Boolean.new.cast ?Impromptu
A
74

I wanted to comment on zetetic answer but as I can't do that yet I'll post this as an answer.

If you use

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

then you can drop ? true : false because params[:show_all] == "1" statement itself will evaluate to true or false and thus ternary operator is not needed.

Azov answered 9/9, 2010 at 9:10 Comment(1)
for future references, this is the cleanest answer. Technically, the boolean can be passed as "true" or "false" but passing a number is better simply because it prevents simple mistakes such as "true" == "True" from returning false. Either 1, or 0.Crispen
P
22

This question is rather old, but since I came across this issue a couple of times, and didn't like any of the solutions proposed, I hacked something myself which allows to use multiple strings for true such as 'yes', 'on', 't' and the opposite for false.

Monkey patch the class String, and add a method to convert them to boolean, and put this file in /config/initializers as suggested here: Monkey Patching in 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

Notice that if the value is none of the valid ones either for true or false, then it returns nil. It's not the same to search for ?paid=false (return all records not paid) than ?paid= (I don't specify if it has to be paid or not -- so discard this).

Then, following this example, the logic in your controller would look like this:

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

It's pretty neat, and helps to keep controllers/models clean.

Perseid answered 31/12, 2012 at 3:48 Comment(5)
An excellent solution, clean, neat and adds a genuinely useful piece of functionality to the String class. Very nice.Padgett
Congratulations, you now have a String#to_bool that can return nil. Please meditate on that for a while, and then either rename the method to something like String#maybe_bool? or resort to ArgumentError.Gooseflesh
This could be simplified even further as def to_bool; ['true', '1', 'yes', 'on', 't'].include? self; endPolyamide
Every time you call this method, you are generating at least one array. Move those arrays into frozen constants. TRUE_VALUES = ['true'.freeze, '1'.freeze, 'yes'.freeze, 'on'.freeze, 't'.freeze]. This a) saves memory and b) prevents runtime alterations of the strings.Pharisaism
@Gooseflesh while I agree with your comment, bear in mind that, at least in Rails 3.2 (haven't tested in other versions), ActiveRecord::ConnectionAdapters::Column.value_to_boolean('') will return nil.Aquarium
H
17
@show_all = params[:show_all] == "1" ? true : false

This should work nicely if you're passing the value in from a checkbox -- a missing key in a hash generates nil, which evaluates to false in a conditional.

EDIT

As pointed out here, the ternary operator is not necessary, so this can just be:

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

Heredes answered 9/9, 2010 at 5:36 Comment(0)
M
4

You could change your equality statement to:

@show_all == "true"

If you want it to be a boolean you could create a method on the string class to convert a string to a boolean.

Molech answered 9/9, 2010 at 4:5 Comment(0)
T
4

I think the simplest solution is to test "boolean" parameters against their String representation.

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

Regardless of whether Rails delivers the parameter as the String "true" or "false" or an actual TrueClass or FalseClass, this test will always work.

Telefilm answered 6/1, 2014 at 19:26 Comment(0)
A
2

You could just do

@show_all = params[:show_all].downcase == 'true'
A answered 1/2, 2020 at 15:38 Comment(0)
A
2

It's worth noting that if you're passing down a value to an ActiveModel in Rails > 5.2, the simpler solution is to use attribute,

class Model
  include ActiveModel::Attributes

  attribute :show_all, :boolean
end

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

As can be seen here.


Before 5.2 I use:

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
Aground answered 18/2, 2020 at 2:12 Comment(0)
A
1

Another approach is to pass only the key without a value. Although using ActiveRecord::Type::Boolean.new.type_cast_from_user(value) is pretty neat, there might be a situation when assigning a value to the param key is redundant.

Consider the following: On my products index view by default I want to show only scoped collection of products (e.g. those that are in the stock). That is if I want to return all the products, I may send myapp.com/products?show_all=true and typecast the show_all parameter for a boolean value.

However the opposite option - myapp.com/products?show_all=false just makes no sense since it will return the same product collection as myapp.com/products would have returned.

An alternative:

if I want to return the whole unscoped collection, then I send myapp.com/products?all and in my controller define

private

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

If the key is present in params, then regardless of its value, I will know that I need to return all products, no need to typecast value.

Araucania answered 8/6, 2016 at 12:45 Comment(1)
I decided to use this approach for my use case. I've been ambivalent about it in the past, but if there is no real use case for the false value, then it makes sense to do it this way!Loxodromics
G
1

You can add the following to your model:

def show_all= value
  @show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
Girand answered 12/8, 2016 at 0:54 Comment(0)
M
0

You could convert all your boolean params to real booleans like this:

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

In this solution, nil parameters would become false.

Mogul answered 15/8, 2012 at 23:10 Comment(0)
L
0

While not explicitly what the question is about I feel this is appropriately related; If you're trying to pass true boolean variables in a rails test then you're going to want the following syntax.

post :update, params: { id: user.id }, body: { attribute: true }.to_json, as: :json

I arrived at this thread looking for exactly this syntax, so I hope it helps someone looking for this as well. Credit to Lukom

Lobation answered 3/2, 2021 at 17:21 Comment(0)
I
0

you can do it like this, below are examples of what will be returned

ActiveModel::Type::Boolean.new.cast('t')     # => true
ActiveModel::Type::Boolean.new.cast('T')     # => true
ActiveModel::Type::Boolean.new.cast('TRUE')  # => true
ActiveModel::Type::Boolean.new.cast('true')  # => true
ActiveModel::Type::Boolean.new.cast(true)    # => true
ActiveModel::Type::Boolean.new.cast('1')     # => true
ActiveModel::Type::Boolean.new.cast('f')     # => false
ActiveModel::Type::Boolean.new.cast('F')     # => false
ActiveModel::Type::Boolean.new.cast('0')     # => false
ActiveModel::Type::Boolean.new.cast('false') # => false
ActiveModel::Type::Boolean.new.cast('FALSE') # => false
ActiveModel::Type::Boolean.new.cast(false)   # => false
ActiveModel::Type::Boolean.new.cast(nil)     # => nil
Icarian answered 10/10, 2023 at 19:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.