ActiveModel::MissingAttributeError on `becomes`
JensDebergh opened this issue · comments
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.
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.
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.
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.
@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.