Need to convert a Boolean from Postgres (== String) to a Ruby Boolean - ruby-on-rails

I'm using Postgres with Rails. There's a query with a subselect which returns a boolean, but Postgres always returns a String like 't' or 'f'. But in the generated JSON I need a real boolean.
This is my query:
SELECT
*,
EXISTS (
SELECT TRUE
FROM measurement
JOIN experiment ON measurement.experiment_id = experiment.id
JOIN location ON experiment.location_id = location.id
WHERE location.map_id = map.id
LIMIT 1
) AS measurements_exist
FROM "map"
It doesn't matter whether I use THEN true or THEN 1 or THEN 'true', I will always get a string. So my JSON response will always look like that:
[
{"id":8, ..., "measurements_exist":"f"},
{"id":9, ..., "measurements_exist":"t"}
]
But it should(!) look like that:
[
{"id":8, ..., "measurements_exist":false},
{"id":9, ..., "measurements_exist":true}
]
Is there any way to get this working right?
Thank you!
THE SOLUTION:
Just give the corresponding model (here: Map) an attribute accessor, which uses value_as_boolean to convert the value. So every time the controller tries to access the value, it uses the attribute accessor method automatically.
The controller code:
class MapsController < ApplicationController
def index
select = ["*"]
select.push(measurements_exist) # This will just insert the string returned by the 'measurements_exist' method
maps = Map.select(select) # Results in 'SELECT *, EXISTS (...) AS measurements_exist FROM "map"'
render json: maps
end
private
def measurements_exist
"EXISTS (
SELECT TRUE
FROM measurement
JOIN experiment ON measurement.experiment_id = experiment.id
JOIN location ON experiment.location_id = location.id
WHERE location.map_id = map.id
LIMIT 1
) AS measurements_exist"
end
end
The model code:
class Map < ActiveRecord::Base
def measurements_exist
ActiveRecord::ConnectionAdapters::Column.value_to_boolean(self[:measurements_exist])
end
end
Resulting JSON:
[
{"id":7, ..., "measurements_exist":false},
{"id":6, ..., "measurements_exist":true}
]

ActiveRecord has a method called ActiveRecord::ConnectionAdapters::Column.value_to_boolean it uses internally to convert any true-like value to a Ruby true value.
You can use it in your code.

When you query model from Rails its boolean fields converted to true/false automatically because DB adapter can determine type of field from schema. If you select custom boolean field from db - adapter doesn't know anything about it, so it returned string 't' or 'f' and you need to convert it manually.
One of the ways to get expected boolean value:
Create the view with provided SQL-query on the DBMS side (e.g. see CREATE VIEW ... statement for PostgreSQL). Views fields have types so boolean fields will be converted in your app automatically. Suppose its called map_with_measurements.
Create model MapWithMeasurement and place it in models/map_with_measurement.rb.
class MapWithMeasurement < ActiveRecord::Base; end
Use MapWithMeasurement.find_all.

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.
class Map < ActiveRecord::Base
def measurements_exist
self[:measurements_exist].to_b
end
end

Here is another solution:
boolean = (value_from_postgres =~ /^t$/i) == 0
converts a value_from_postgres of 't' to boolean true or 'f' to boolean false
$irb
2.2.1 :001 > value_from_postgres = 't'
=> "t"
2.2.1 :002 > (value_from_postgres =~ /^t$/i) == 0
=> true
2.2.1 :003 > value_from_postgres = 'f'
=> "f"
2.2.1 :004 > (value_from_postgres =~ /^t$/i) == 0
=> false
The regular expresion in this line could be modified to match /^true$/i if you are expecting a string "true" or "false". This is more flexible than using a ternary or a gem, because you can write it to convert a match to any regex to a boolean true.
Using a ternary it looks like:
boolean = value_from_postgres.eql?('t') ? true : false

Related

How can I cast a enum string value to column type?

I need to create a method that dynamically filters a model by a column. It needs to receive the column that I want to filter by (called attr_name), an operator as a string, and the value as a string.
I need to first cast the string value to the database column type so I can then do the sql query.
scope :filtered_by_attribute, (lambda do |attr_name, operator, value|
comparing_value = Customer.attribute_types[attr_name].cast(value)
casting_error = !value.nil? && comparing_value.nil?
raise I18n.t('api.errors.unaplicable_customer_scope') if casting_error
sql_query = sanitize_sql("#{attr_name} #{operator} ?")
where(sql_query, comparing_value)
end)
The problem above is when it comes to enums. Enums are integers on the db but when I perform the casting it will return the same string value since for rails it is a string. Then, in the where query, it explodes since in the database it's comparing an integer column with a string.
Do you know how I cast a string value to match the type of a column in the database?
Thank you!
The cast method casts a value from user input when it is assigned to an instance. In the case of enum when you assign a string value it remains a string value. It is converted to an integer only when it is persisted in DB.
class Order < ActiveRecord::Base
enum status: {confirmed: 1, cancelled: 2}
end
# this is where the `cast` method is called
#order.status = "cancelled"
# still a string since the `cast` method didn't do anything.
#order.status # => "cancelled"
What you really need is the serialize method. It casts a value from a ruby type to a type that the database knows how to understand.
Order.attribute_types["status"].serialize("cancelled") # => 2

Rails- when doing a multiple condition search, how to eliminate the column with nil value?

A car has three attributes, "model", "location", "status". I want to search cars according these three conditions. First, I thought I could use
Car.where(model: params[:car][:model], location: params[:car][:location], status: params[:car][:status])
However, I do not know how to eliminate the column with nil value. For example, if I do not input model which means params[:car][:model] = nil, it would actually do select * from car where model = null and status = ? and location = ?
However, what I want is select * from car where location= ? and status = ?. How could I make this happen?
First you want to slice out the parameters you want:
# `.fetch(:car, {})` prevents a nil error if the key is not present in the params.
params.fetch(:car, {}).permit(:location, :status, :model)
# or if `params[:car]` is required.
params.require(:car).permit(:location, :status, :model)
Then use Hash#compact which returns a new hash with nil values removed:
irb(main):008:0> {"location"=>1, "status"=>nil, "model"=>"Ford Model T"}.compact
=> {"location"=>1, "model"=>"Ford Model T"}
Altogether:
Car.where(params.fetch(:car, {}).slice(:location, :status, :model).compact)

Return value of the overridden attribute accessor

I just found that, when I override the boolean setter of a Rails model, the return value behaves unexpectedly.
Here is a simple User model where is_admin is a boolean field. What I want is pretty straightforward: cast String 'T', 'F' into true, false respectively.
class User < ApplicationRecord
def is_admin=(value)
case value
when 'T'
super(true)
when 'F'
super(false)
else
super
end
end
end
The cast works fine. u = User.new; u.is_admin = 'T' does set u.is_admin to true. However the expression
u.is_admin = 'T'
returns 'T' rather than true.
This is easily reproduced by creating a brand new Rails 5 project and create a model named User with only one column is_admin as boolean. After that, override :is_admin as I did, you will see what I saw.
Any idea on the weird return value? Thanks!
Assignment operator always returns the right hand value. Simple as that. Observe:
class Foo
def bar=(val)
#bar = val.to_s # => "1234"
end
end
f = Foo.new
f.bar = 1234 # => 1234

How do I submit a boolean parameter in Rails?

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?
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
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.
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.
#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"
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.
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.
You could just do
#show_all = params[:show_all].downcase == 'true'
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
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.
You can add the following to your model:
def show_all= value
#show_all = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value)
end
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.
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

Is it possible to have variable find conditions for both the key and value?

I'm trying to pass in both the field and the value in a find call:
#employee = Employee.find(:all,
:conditions => [ '? = ?', params[:key], params[:value].to_i)
The output is
SELECT * FROM `employees` WHERE ('is_manager' = 1)
Which returns no results, however when I try this directly in mysqsl using the same call without the '' around is_manager, it works fine. How do I convert my params[:key] value to a symbol so that the resulting SQL call looks like:
SELECT * FROM `employees` WHERE (is_manager = 1)
Thanks,
D
If you want to convert a string to symbol(which is what params[:key] produces, all you need to do is
params[:key].to_s.to_sym
2 points:
A word of caution : symbols are
not garbage collected.
Make sure your key is not a
number, if you convert to_s first
then to_sym, your code will work but
you may get a wierd symbol like
this:
:"5"
You could use variable substitution for column name instead of using bind values:
# make sure the key passed is a valid column
if Employee.columns_hash[params[:key]]
Employee.all :conditions => [ "#{params[:key]} = ?", params[:value]]
end
You can further secure the solution by ensuring column name passed belongs to a pre selected set.:
if ["first_name", "last_name"].include? [params[:key]]
Employee.all :conditions => [ "#{params[:key]} = ?", params[:value]]
end
"string".to_sym

Resources