rails: "undefined method `accessor'" after attempting to reverse an PG db with hstore

Pretty simple repro on Rails 4.1.5 and PostgreSQL:

  1. Add hstore to an app being sure to enable the hstore extension* and change the schema format to sql in application.rb
  2. Create a model and table that use hstore with store_accessor (i.e. store_accessor :specs, :color, :material, :shape
  3. Migrate your db

(At this point everything works.)

  1. Rollback your db migrations (i.e. rake db:migrate VERSION=0)
  2. Migrate forward again

At this point anything that uses one of the specs causes and “undefined method `accessor’” exception in activerecord lib/active_record/connection_adapters/postgresql_adapter.rb.

If between steps 3 and 4 you drop and recreate the database, everything works again.

It’s possible I had this problem in Rails 4.1.2 but I just didn’t encounter it.

  • I have a migration that runs first, it enables the hstore extension on up and disables it on down.

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Comments: 28 (14 by maintainers)

Most upvoted comments

Hello, I’ve got this issue yet with Rails 5.0.1, easiest way to reproduce, is this chunk

  module Foo
    extend ActiveSupport::Concern

    included do
      scope :last_updated_at, -> { order(updated_at: :desc).limit(1).last&.updated_at }
      self.last_updated_at
    end
  end
 
class Bar < ActiveRecord::Base
  include Foo
  store_accessor :settings, :s1, :s2
end
Bar.first.s1 # => NoMethodError: undefined method `accessor' for #<ActiveModel::Type::Text

Same here with Rails 5.2.1!

Well I have the same issue…the difference is that on my local it actually works fine, but when I deploy to Heroku I’m getting NoMethodError (undefined method 'accessor' for #<ActiveModel::Type::Value:.....>)

running Rails 5.0.2, PG 0.20.0

I updated PG to 0.21.0 and now it works on both environments. Still not entirely clear to me

Typing heroku restart or git push heroku main -f fixed the issue for me. As well initially I had the code in a concern. When I moved it to the model & pushed to production it also started working.

So I found an answer to this. I will write a better answer here soon and I’m writing a small blogpost about this to make sure it is covered somewhere.

What happened

In the meantime, here is what happened to me, the chronology is important:

  • I successfully created the hstore needed in the db, and migrated.

  • I added the rails line allowing this hstore to store “JSON attributes” i.e.: store :hstore_column_name, accessors: [ :attribute_one_name, ...]

  • I then reverted the migration as I needed to change something.

  • I made my modifications in the migration, then tried to re-run migrate.

  • This is when the “bug” (which is not one) appeared.

What is happening is that the line declaring the hstore is still in the model at this point. When Rails tries to run the migration, the model is called, and this line throws an error, as this DB column is supposed to be used for hstore, but does not exist yet.

Quick Fix

My quick fix was to remove the hstore declaration in the model and to migrate, then re-add it.

Better solution

A better solution to make hstore migrations fully reversible, allowing for model evolutions (if this hstore disappears a year from now and a newcomer needs to migrate their DB to the latest), and allowing for data population at the same time requires 3 migrations, where the hstore declaration is declared for the model inside the migration file. I’m working on a clean explanation, and will gladly take feedback from people in this thread, I don’t have the pretension to say I will come up with the best way by myself.

I hope this helps, but let me know if this is not what is happening.

@herophuong this is not something we can solve. That’s the reason why you should not use your applications models in migrations. It’s better to define a throw-away model inside your migration class and make sure that one has the right configuration:

class ConvertToJsonInPlayers < ActiveRecord::Migration
  class MyModel < ActiveRecord::Base
    self.table_name = "the_table"
    store :avatar, accessors: %i(small_avatar medium_avatar large_avatar), coder: JSON
  end

  def up
    # ...
  end
end