Подтвердить что ты не робот

Получение типизированных результатов из ActiveRecord raw SQL

В Sequel я могу сделать:

irb(main):003:0> DB["select false"].get
=> false

Что возвращает ложное логическое значение. Я хотел бы иметь возможность сделать что-то подобное в ActiveRecord:

irb(main):007:0> ActiveRecord::Base.connection.select_value "select false"
=> "f"

Как вы можете видеть, он возвращает строку "f". Есть ли способ получить ложное логическое значение с помощью ActiveRecord? (Аналогично, я мог бы вызвать функцию, которая возвращает timestamptz, массив и т.д. - я бы хотел, чтобы возвращаемое значение имело правильный тип)

Мой вариант использования: я вызываю функцию базы данных, хочу вернуть типизированный результат вместо строки.

4b9b3361

Ответ 1

Довольно уродливый, но делает то, о чем вы просите:

res = ActiveRecord::Base.connection.
select_all("select 1 as aa, false as aas, 123::varchar, Array[1,2] as xx")

# Breaks unless returned values have unique column names
res.map{|row|row.map{|col,val|res.column_types[col].type_cast val}}

# Don't care about no column names
res.map{|row|
  row.values.map.with_index{|val,idx|
    res.column_types.values[idx].type_cast val
  }
}

дает:

[[1, false, "123", [1, 2]]]

Как это работает:

res.column_types

возвращает хеш имен столбцов и типов столбцов Postgresql

Вот указатель на то, как он работает: https://github.com/rails/docrails/blob/fb8ac4f7b8487e4bb5c241dc0ba74da30f21ce9f/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb

Ответ 2

Хотя я не сомневаюсь, что ответ Бьорна Нильсона работал, когда он опубликовал его, он терпит неудачу для меня с Postgres 9.4 и версией gg PG 0.18.2. Я нашел следующее для работы после просмотра документации по жемчужине PG:

pg = ActiveRecord::Base.connection
@type_map ||= PG::BasicTypeMapForResults.new(pg.raw_connection)

res = pg.execute("SELECT 'abc'::TEXT AS a, 123::INTEGER AS b, 1.23::FLOAT;")
res.type_map = @type_map
res[0]
# => {"a"=>"abc", "b"=>123, "float8"=>1.23}

Ответ 3

Я не знаю, так ли это, но вы можете создать модель activerecord без таблицы с каким-то поддельным столбцом:

class FunctionValue < ActiveRecord::Base
  def self.columns
    @columns ||= [];
  end

  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(
      name.to_s,
      default,
      sql_type.to_s,
      null
    )
  end

  column :value, :boolean
end

И затем вы можете запустить это:

function_value = FunctionValue.find_by_sql('select false as value').first
function_value.value

Ответ 4

У вас недостаточно очков репутации для ответа, но ответ Bjorn и связанные с ним ответы разбиты на Rails 5. Это работает:

res = ActiveRecord::Base.connection.select_all(sql)
res.to_a.map{|o| o.each{|k, v| obj[k] = res.column_types[k].cast v}}

Ответ 5

Это работает для меня в рельсах 5

results = ActiveRecord::Base.connection.select_all(sql)
results.rows.map{ |row| Hash[results.columns.zip(row)] }

Дает приятные результаты

[{"person1"=>563, "person2"=>564, "count"=>1},
 {"person1"=>563, "person2"=>566, "count"=>5},
 {"person1"=>565, "person2"=>566, "count"=>1}]