Преобразование столбца HStore в массив

У меня есть столбец в моем приложении Rails/Postgres, который я хочу изменить с Hstore на массив. (Я хранил телефоны в виде хэша, поэтому мог сделать {default: 123, mobile: 1234}, но решил, что это не нужно/полезно)

Поэтому я сделал следующую миграцию:

class ChangePhonesToArray < ActiveRecord::Migration
  def up
    new_phones = {}
    Place.find_each do |p|
      new_phones[p.id] = p.phones.values.map{ |v| v.gsub(%r!\D!, '') } # get rid of non-number characters while I'm at it
    end

    remove_column :places, :phones

    add_column :places, :phones, :string, array: true, default: []

    new_phones.each do |k, v|
      p = Place.find(k)
      p.update_attributes!(phones: v)
    end
  end
  ...
end

Однако, когда я это делаю, я получаю эту неприятную ошибку БД, предполагающую, что phones все еще является столбцом Hstore!

StandardError: An error has occurred, this and all later migrations canceled:

can't cast Array to hstore/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/quoting.rb:76:in `type_cast'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql/quoting.rb:111:in `type_cast'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:828:in `block in exec_cache'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:827:in `map'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql_adapter.rb:827:in `exec_cache'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:155:in `exec_delete'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/database_statements.rb:101:in `update'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `update'

Я предположил, что столбец не был удален правильно, поэтому я добавил второй remove_column сразу после первого, и это вызвало приведенную ниже ошибку, предполагающую, что столбец уже был удален! (но видимо не полностью).

StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedColumn: ERROR:  column "phones" of relation "places" does not exist
: ALTER TABLE "places" DROP "phones"/Users/sasha/.rvm/gems/ruby-2.1.2/gems/rack-mini-profiler-0.9.2/lib/patches/sql_patches.rb:160:in `exec'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/rack-mini-profiler-0.9.2/lib/patches/sql_patches.rb:160:in `async_exec'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:128:in `block in execute'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract_adapter.rb:373:in `block in log'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/abstract_adapter.rb:367:in `log'
/Users/sasha/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:127:in `execute'

Любая идея, что здесь происходит, и как это исправить/обойти?


person Sasha    schedule 13.01.2015    source источник


Ответы (1)


То, что вы изменили схему таблицы внутри базы данных, не означает, что ваш класс Place знает об этих изменениях. ActiveRecord::Base подклассы загружают информацию о столбце только один раз, чтобы избежать ненужного обращения к базе данных снова и снова. Как только вы это сделаете:

Place.find_each

ваш Place будет знать типы столбцов. Затем вы меняете схему за спиной Place и пытаетесь записать новые значения, но Place не знает об изменениях. Обычно для этого используется вызов reset_column_information. :

Сбрасывает всю кэшированную информацию о столбцах, что приводит к их перезагрузке при следующем запросе.

Наиболее распространенный шаблон использования этого метода, вероятно, связан с миграцией...

так что вы бы просто сказать:

#...
remove_column :places, :phones
add_column :places, :phones, :string, array: true, default: []
Place.reset_column_information
#...

Кстати, нет никакой гарантии, что hstore будет поддерживать какой-либо порядок, поэтому вы можете захотеть p.phones.values_at(:default, :mobile) вместо p.phones.values, чтобы убедиться, что в вашем массиве все в правильном порядке.

person mu is too short    schedule 15.01.2015
comment
Спасибо! Я понятия не имел, что самому классу нужно сбросить информацию. Круто учиться. Работал как шарм. - person Sasha; 15.01.2015