需要将Postgres(== String)中的布尔值转换为Ruby布尔值

我正在使用Postgres和Rails。 有一个带有subselect的查询返回一个布尔值,但Postgres总是返回一个像't''f'的字符串。 但是在生成的JSON中我需要一个真正的布尔值。

这是我的查询:

 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" 

无论我是使用THEN true还是THEN 1THEN 'true'都没关系,我总会得到一个字符串。 所以我的JSON响应总是这样:

 [ {"id":8, ..., "measurements_exist":"f"}, {"id":9, ..., "measurements_exist":"t"} ] 

但它应该(!)看起来像那样:

 [ {"id":8, ..., "measurements_exist":false}, {"id":9, ..., "measurements_exist":true} ] 

有没有办法让这个工作正常?

谢谢!


解决方案:

只需给对应的模型(这里: Map )一个属性访问器,它使用value_as_boolean来转换值。 因此,每次控制器尝试访问该值时,它都会自动使用属性访问器方法。

控制器代码:

 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 

型号代码:

 class Map < ActiveRecord::Base def measurements_exist ActiveRecord::ConnectionAdapters::Column.value_to_boolean(self[:measurements_exist]) end end 

结果JSON:

 [ {"id":7, ..., "measurements_exist":false}, {"id":6, ..., "measurements_exist":true} ] 

ActiveRecord有一个名为ActiveRecord::ConnectionAdapters::Column.value_to_boolean它在内部使用它将任何类似true的值转换为Ruby true值。

您可以在代码中使用它。

当您从Rails查询模型时,其布尔字段会自动转换为true / false,因为DB适配器可以确定模式中的字段类型。 如果从db中选择自定义布尔字段 – 适配器对它没有任何了解,那么它返回字符串’t’或’f’,您需要手动转换它。

获得预期布尔值的方法之一:

  1. 在DBMS端使用提供的SQL查询创建视图 (例如,参见PostgreSQL的CREATE VIEW …语句)。 视图字段具有类型,因此布尔字段将自动在您的应用中转换。 假设它叫map_with_measurements

  2. 创建模型MapWithMeasurement并将其放在models/map_with_measurement.rbclass MapWithMeasurement < ActiveRecord::Base; end

  3. 使用MapWithMeasurement.find_all

你可以使用wannabe_bool gem。 https://github.com/prodis/wannabe_bool

这个gem实现了String,Integer,Symbol和NilClass类的#to_b方法。

 class Map < ActiveRecord::Base def measurements_exist self[:measurements_exist].to_b end end 

这是另一个解决方案:

 boolean = (value_from_postgres =~ /^t$/i) == 0 

将’t’的value_from_postgres转换为布尔值true或将’f’转换为布尔值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 

如果您期望字符串“true”或“false”,则可以修改此行中的常规表达式以匹配/ ^ true $ / i。 这比使用三元组或gem更灵活,因为您可以编写它以将匹配转换为任何正则表达式为布尔值true。

使用三元组看起来像:

 boolean = value_from_postgres.eql?('t') ? true : false