jrochkind / attr_json

Serialized json-hash-backed ActiveRecord attributes, super smooth

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ActiveModel::MissingAttributeError on `becomes`

JensDebergh opened this issue · comments

commented

Hi Jonathan

I noticed you recently deployed the 2.0 version of attr_json. We migrated successfully to 2.0.0 while still on 6.x.

I've recently started the upgrade to 7.x and noticed my test suite is failing on a really specific use case using single table inheritance.

We're using single table inheritance for an integrations feature.

Base class:

class Integration < ApplicationRecord
  include AttrJson::Record

  attr_json_config(default_container_attribute: :config)
end

Integration class:

module Integrations
  class Unpaid < Integration
    attr_json :client_id, :string # <--- client_id for getters/setters defined
  end
end

In a controller or service object we would do this:

integration = Integration.new(params).becomes(Integrations::#{type}.constantize) # type => Unpaid
integration.client_id = "Hello world"
# raise ActiveModel::MissingAttributeError for client_id

Writing it like this does work:

integration = Integrations::#{type}.constantize.new(params)
integration.client_id = "hello world"
integration.save

I haven't looked at the internals of attr_json but my best bet is that becomes does not trigger the re-initialization of the attr_json properties set on a subclass.

Depending on the selected subclass different attributes are exposed (which is the main reason why we use attr_json)

I just wanted to let you know that something is going "wrong" there and to see if this is expected behaviour or not.

If I can be of any help just let me know, I'd be happy to help if you can point me in the right direction.

Kind regards
Jens Debergh

I am about to be on vacation for 10 days, so it may be a while until I get to this. But I'll try to get back to it -- please feel free to ping me again by commenting here in a couple weeks, if I haven't gotten back to it.

I don't ever use the becomes feature myself. Although from what I know of it I wouldn't expect a problem, perhaps there is one!

It does not sound like expected behavior of course.

the code you have already provided is a good start! If you are able to create an isolated reproduction, either in an actual rspec test case, or just pasting it in somewhere or providing me with a sample app -- that would give me a head-start! Otherwise I'll see if I can turn your code into a failing rspec test myself!

When you talk about "I've recently started the upgrade to 7.x", you're talking about Rails 7.0? And you're saying the bug does not exhibit in Rails 6.1, but does in Rails 7.0? I wonder if there is a bug in Rails 7.0 with Single-Table Inheritance, that is not about attr_json. If we discover one that has not yet been reported to Rails, we can report it, and still also see if we can work around it.

commented

No worries. We just adapted our code to not use becomes. I just wanted to raise the issue.

Short term we already managed to fix it, but I like the library that's why I'm also posting this issue and hopefully (if there is one, we can solve it together)

I don't have the time during the day to track down the issue, but I'll attempt it tonight.

Enjoy your holidays! 🌴

@guilhermegazzinelli I've moved your comment to a separate issue, because its' not clear to me it's the same issue. here: #190

I am back from vacation and will try to explore both of these reports more this week, see if I can reproduce or underestand.

@JensDebergh

Back from my time off, looking at this.

I'm actually getting a DIFFERENT error on becomes, any becomes. In attr_json 2.0.0, Rails 7.0.4.2.

  1) Integration becomes
     Failure/Error: Integration.new({}).becomes(Integrations::Unpaid)

     NoMethodError:
       undefined method `[]' for nil:NilClass
     # /Users/jrochkind/.gem/ruby/3.0.5/gems/attr_json-2.0.0/lib/attr_json/record.rb:55:in `block in attr_json_sync_to_rails_attributes'
     # /Users/jrochkind/.gem/ruby/3.0.5/gems/attr_json-2.0.0/lib/attr_json/record.rb:51:in `each'
     # /Users/jrochkind/.gem/ruby/3.0.5/gems/attr_json-2.0.0/lib/attr_json/record.rb:51:in `attr_json_sync_to_rails_attributes'
     # /Users/jrochkind/.gem/ruby/3.0.5/gems/attr_json-2.0.0/lib/attr_json/record.rb:30:in `block (2 levels) in <module:Record>'
     # ./spec/models/integeration_spec.rb:5:in `block (2 levels) in <top (required)>'

What version of Rails are you using?

What is actually in your params in your example? I'm guessing it has a key/value for config? Which avoided this bug, so you could get to the second bug.

I want to fix things for becomes as much as i can; but also want to reproduce with Rails STI and attributes without attr_json, my hypothesis right now is it might be a Rails bug/limitation.

@JensDebergh

Okay, yep, I've reproduced, but I've also reproduced with pure-Rails virtual attributes, without attr_json being involved at all.

class Integration < ApplicationRecord
end

module Integrations
  class Unpaid < Integration
    attribute :virtual_string, :string
  end
end

direct_unpaid = Integrations::Unpaid.new
direct_unpaid.virtual_string = "my value"
direct_unpaid.virtual_string # => "my value"

# but:

integration = Integration.new
became_unpaid = integration.becomes(Integrations::Unpaid)
became_unpaid.virtual_string = "foo"
# raises ActiveModel::MissingAttributeError

/Users/jrochkind/.gem/ruby/3.0.5/gems/activemodel-7.0.4.2/lib/active_model/attribute.rb:211:in 'with_value_from_database': can't write unknown attribute 'virtual_string' (ActiveModel::MissingAttributeError)

I'm going to try reporting this to Rails. Then think if we can work around somehow, not sure.

Were you using become like this in attr_json 1.x? If so, I'm guessing you were not using the rails_attribute: true feature?

@JensDebergh Thanks for reporting this by the way!

OK, I've filed with Rails at rails/rails#47538

I am not expecting much to happen on Rails end. If we can get anyone's attention at all, I would not be surprised if they say "Sorry, you just can't use becomes like this." Single-Table Inheritance in general sometimes seems like something Rails maintainers wish wasn't there! (I'm glad it is, I find it very useful, especially with attr_json).

With Rails behaving this way, I'm not sure there's much we can do about it on this end.

Out of curiosity, I checked the "peer" project, https://github.com/madeintandem/jsonb_accessor#single-table-inheritance -- it does have exactly the same problem you ran into with attr_json and becomes here, as it also uses Rails attributes similarly under the hood.

I suppose I could put something in the README warning that becomes may not work due to Rails limitations.

I am still curious how/if this was ever working for you -- I am guessing if it was working you in attr_json 1.x, you were not using the rails_attribute: true feature (which is effectively forced for you in attr_json 2.x), I'm interested in hearing confirmation!

Ooh and good news from the Rails issue report, which did get attention! The Rails issue was identified as a bug and actually is already fixed on Rails main branch, which means in Rails 7.1 it should be fixed. it's unclear if it will be backported to Rails 7.0.

When a Rails release that fixes it happens, I would like to add a test to attr_json verifying becomes works, in those Rails versions where it can.

commented

@jrochkind Apologies for the delay (I have a newborn and do see the message, but don't have the time sometimes)

That's great news actually! Thanks for working on this so fast. (it was not a huge dealbreaker for us)

But this was something that worked before the upgrade, that's why a posted it!

Thank you for the quick response 🙏

No need for any apologies, we're not on a schedule here, I waited two weeks before getting back to you, which is still pretty good!

The Rails bug had actually already been reported and fixed by other people in Rails.

I am still curious for you to confirm -- you didn't ever use rails_attribute: true in attr_json 1.x, yes? If you did I'd be curious why you didn't run into the bug, and maybe want to learn more.

I added a note to the README on the Rails bug, although there's so much in the README I don't know if anyone would find it.

But I'm inclined to close this issue presently.