byroot / activerecord-typedstore

ActiveRecord::Store but with type definition
MIT License
439 stars 57 forks source link

undefined method 'accessor' for ActiveRecord::Type::Value #33

Closed Antiarchitect closed 8 years ago

Antiarchitect commented 8 years ago

Rails 4.2.4, activerecord-typedstore 0.6.1. When I try to instanciate record with decimal typed value described. I get this:

NoMethodError: undefined method `accessor' for #<ActiveRecord::Type::Value:0x0000000a097270>
    from /home/andrey/.rvm/gems/ruby-2.2.3@medm/gems/activerecord-4.2.4/lib/active_record/store.rb:132:in `store_accessor_for'
    from /home/andrey/.rvm/gems/ruby-2.2.3@medm/gems/activerecord-4.2.4/lib/active_record/store.rb:121:in `read_store_attribute'
    from /home/andrey/PROJECTS/activerecord-typedstore/lib/active_record/typed_store/extension.rb:118:in `write_store_attribute'
byroot commented 8 years ago

That's strange. Decimals are part of the test suite :/

I'll see what I can do, but a reproduction gist would tremendously help.

You could base it off Rails's repro template: https://github.com/rails/rails/blob/adfb823af52d368fa4d88731a9809a314ad884ad/guides/bug_report_templates/active_record_gem.rb

Antiarchitect commented 8 years ago

@byroot Here you are. This perfectly reproduces my real problem.

begin
  require 'bundler/inline'
rescue LoadError => e
  $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
  raise e
end

gemfile(true) do
  source 'https://rubygems.org'
  # Activate the gem you are reporting the issue against.
  gem 'activerecord', '4.2.4'
  gem 'sqlite3'
  gem 'activerecord-typedstore', '0.6.1'
end

require 'active_record'
require 'minitest/autorun'
require 'logger'

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.text :data
  end
end

class Post < ActiveRecord::Base

  module DummyCoder
    extend self

    def load(data)
      data || {}
    end

    def dump(data)
      data || {}
    end
  end

  typed_store :meta, coder: DummyCoder do |s|
    s.decimal :value
  end
end

class BugTest < Minitest::Test
  def test_association_stuff
    post = Post.create!(value: 36.6)
    post.comments << Comment.create!

    assert_equal 1, post.comments.count
  end
end

This is my output:

Fetching gem metadata from https://rubygems.org/...........
Fetching version metadata from https://rubygems.org/..
Resolving dependencies...
Using i18n 0.7.0
Using json 1.8.3
Using minitest 5.8.1
Using thread_safe 0.3.5
Using tzinfo 1.2.2
Using activesupport 4.2.4
Using builder 3.2.2
Using activemodel 4.2.4
Using arel 6.0.3
Using activerecord 4.2.4
Using activerecord-typedstore 0.6.1
Using sqlite3 1.3.10
Using bundler 1.10.3
-- create_table(:posts, {:force=>true})
D, [2015-09-24T21:34:58.259118 #13651] DEBUG -- :    (0.2ms)  CREATE TABLE "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "data" text) 
   -> 0.0022s
Run options: --seed 24322

# Running:

E

Finished in 0.003298s, 303.2433 runs/s, 0.0000 assertions/s.

  1) Error:
BugTest#test_association_stuff:
NoMethodError: undefined method `accessor' for #<ActiveRecord::Type::Value:0x00000003068260>
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/store.rb:132:in `store_accessor_for'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/store.rb:121:in `read_store_attribute'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-typedstore-0.6.1/lib/active_record/typed_store/extension.rb:116:in `write_store_attribute'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/store.rb:86:in `block (3 levels) in store_accessor'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:54:in `public_send'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:54:in `_assign_attribute'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:41:in `block in assign_attributes'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:35:in `each'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/attribute_assignment.rb:35:in `assign_attributes'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/core.rb:564:in `init_attributes'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/core.rb:281:in `initialize'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/inheritance.rb:61:in `new'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/inheritance.rb:61:in `new'
    /home/andrey/.rvm/gems/ruby-2.2.2/gems/activerecord-4.2.4/lib/active_record/persistence.rb:50:in `create!'
    bug.rb:54:in `test_association_stuff'

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
byroot commented 8 years ago
t.text :data
typed_store :meta

:data != :meta. So I think it's simply due to a typo. Please re-open if I'm wrong.

Antiarchitect commented 8 years ago

@byroot Reproduced more thoroughly. Seems like it's because of delegate.

begin
  require 'bundler/inline'
rescue LoadError => e
  $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
  raise e
end

gemfile(true) do
  source 'https://rubygems.org'
  # Activate the gem you are reporting the issue against.
  gem 'activerecord', '4.2.4'
  gem 'sqlite3'
  gem 'activerecord-typedstore', '0.6.1'
end

require 'active_record'
require 'minitest/autorun'
require 'logger'

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.integer :author_id
    t.text :data
  end
  create_table :authors, force: true do |t|
  end
end

class Post < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_one :post

  module DummyCoder
    extend self

    def load(data)
      data || {}
    end

    def dump(data)
      data || {}
    end
  end

  delegate :data, :data_will_change!, :data_changed?, to: :bound_post

  def bound_post
    @bound_post ||= build_post
  end

  typed_store :data, coder: DummyCoder do |s|
    s.decimal :value
  end
end

class BugTest < Minitest::Test
  def test_association_stuff
    Author.create!(value: 36.6)

    assert_equal 1, Author.count
  end
end
byroot commented 8 years ago

Oh! I see. You can't define a store on something that isn't a real attribute. TypedStore need to read the column information from ActiveRecord.

If you really wish to delegate this column, you can still do it this way:

begin
  require 'bundler/inline'
rescue LoadError => e
  $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
  raise e
end

gemfile(true) do
  source 'https://rubygems.org'
  # Activate the gem you are reporting the issue against.
  gem 'activerecord', '4.2.4'
  gem 'sqlite3'
  gem 'byebug'
  gem 'activerecord-typedstore', '0.6.1'
end

require 'active_record'
require 'minitest/autorun'
require 'logger'

# Ensure backward compatibility with Minitest 4
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.integer :author_id
    t.text :data
  end
  create_table :authors, force: true do |t|
  end
end

class Post < ActiveRecord::Base
  belongs_to :author

  typed_store :data do |s|
    s.decimal :value
  end
end

class Author < ActiveRecord::Base
  has_one :post

  delegate :value, :value=, to: :bound_post

  def bound_post
    @bound_post ||= build_post
  end
end

class BugTest < Minitest::Test
  def test_association_stuff
    Author.create!(value: 36.6)

    assert_equal 1, Author.count
  end
end
Antiarchitect commented 8 years ago

Exactly, this is my current workaround. But this only works when your bound model class and parent class is one to one.

Antiarchitect commented 8 years ago

Think for normal situations that is not a bug.