byroot/activerecord-typedstore

NoMethodError: undefined method `cast' for nil:NilClass after set value to field

havran opened this issue · 3 comments

Hello. Thanks for great gem!

I have weird problem in my production app.

If i try set value for field url in new or stored model, i always get NoMethodError: undefined method `cast' for nil:NilClass - but only for one field from all typed_Store defined fields.

Rails version is 5.x

In development mode works all ok.

module Space
  module ProjectItems
    class Page < ::Space::ProjectItem
      ...
      typed_store :data, coder: JsonCoder do |d|
        d.string  :valid_for_region,  array: true, default: []
        d.string  :category,                       default: nil
        d.string  :tag,               array: true, default: []
        d.string  :hashtag,                        default: nil
        d.string  :url,                            default: nil   # URL
      end
      ...
    end
  end
end
irb(main):044:0*  p = Space::ProjectItems::Page.new
=> #<Space::ProjectItems::Page id: nil, project_id: nil, item_type: "page", title: nil, description: nil, public: false, valid_from: nil, valid_to: nil, image_data: nil, data_location: {"lat"=>nil, "lon"=>nil, "zoom"=>nil, "formatted_address"=>nil}, data_zones: {"zones"=>[], "use_location_from_zones"=>false}, data: {"valid_for_region"=>[], "category"=>nil, "tag"=>[], "hashtag"=>nil, "url"=>nil}, created_at: nil, updated_at: nil, code: nil>
irb(main):045:0> p.data
=> {"valid_for_region"=>[], "category"=>nil, "tag"=>[], "hashtag"=>nil, "url"=>nil}
irb(main):046:0> p.category = 'a'
=> "a"
irb(main):047:0> p.hashtag = 'b'
=> "b"
irb(main):048:0> p.url = 'c'
NoMethodError: undefined method `cast' for nil:NilClass
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/activerecord-typedstore-1.1.1/lib/active_record/typed_store/extension.rb:74:in `write_store_attribute'
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/activerecord-5.0.3/lib/active_record/store.rb:91:in `block (3 levels) in store_accessor'
        from (irb):48
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/railties-5.0.3/lib/rails/commands/console.rb:65:in `start'
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/railties-5.0.3/lib/rails/commands/console_helper.rb:9:in `start'
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/railties-5.0.3/lib/rails/commands/commands_tasks.rb:78:in `console'
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/railties-5.0.3/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
        from /home/deployer/uidis/shared/bundle/ruby/2.3.0/gems/railties-5.0.3/lib/rails/commands.rb:18:in `<top (required)>'
        from bin/rails:9:in `require'
        from bin/rails:9:in `<main>'

I try refactor my code. Before model Page is inherited from base model ProjectItems (models shared same database table, but i want different fields in store on every model). Now i have common model settings in concern and problem with set value on fields disappear.

I am still curious with this but i think this is burried too deep for my knowledge :-/

Could you try reproduce the issue with a minimal app?

I have the same problem which present itself only when model inheritance is used and descendant defines it's own typed_store. This has an unexpected effect of overriding typed_store value for same store_attribute for all class hierarchy and ancestors in particular.

For example this code results in all animals having gills:

class Animal < ApplicationRecord
  typed_store :data do |d|
    d.integer :ears_count, default: 2
  end
end

class Fish < Animal
  typed_store :data do |d|
    d.boolean :has_gills, default: true
  end
end

Animal.new.has_gills 
# => true
Animal.new.ears_count
# NoMethodError: undefined method `ears_count'

Desired behaviour for me is this:

class Animal < ApplicationRecord
  typed_store :data do |d|
    d.integer :ears_count, default: 2
  end
end

class Bear < Animal
end

class Fish < Animal
  typed_store :data do |d|
    d.boolean :has_gills, default: true
  end
end

Animal.new.ears_count
# => 2
Animal.new.has_gills 
# NoMethodError: undefined method `has_gills'

Bear.new.ears_count
# => 2
Bear.new.has_gills
# NoMethodError: undefined method `has_gills'

Fish.new.has_gills 
# => true
Fish.new.ears_count
# NoMethodError: undefined method `ears_count'

There are one possible solution in previous commit. What are your thoughts?