madeintandem / jsonb_accessor

Adds typed jsonb backed fields to your ActiveRecord models.
MIT License
1.11k stars 93 forks source link

Add support for deeply nested objects/hashes #69

Closed DavidHooper closed 3 years ago

DavidHooper commented 7 years ago

I'm almost sold on using this project for rapid prototyping. However one of the blockers is that there is currently no way to define nested attributes or deeply nested attributes.

Is deeply nested attributes suitable for this library and how might it be implemented if so?

crismali commented 7 years ago

This was a feature we had in the 0.X.X versions of the gem (README for the most recent version with this support). We dropped support for it because it got pretty complicated very quickly. There's a section in the upgrade guide about what the gem used to do and a bit about how you could go about implementing it. (Short version is that you'll end up making a class that behaves like an ActiveRecord::Type, then register it with ActiveRecord::Type, and then you should be able to do something like jsonb_accessor :data, foo: :my_awesome_nested_type. I imagine the fastest way would involve OpenStruct but if I remember right it won't give you NoMethodErrors.)

DavidHooper commented 7 years ago

I was hoping to do through some sort of declarative syntax like:

jsonb_accessor :data, 
               foo: {
                 bar: [:integer, default: 0],
                 foobar: {
                   boofar: [:string, default: 'my default']
                 }     
               }
crismali commented 7 years ago

The previous version worked similarly, it just became pretty cumbersome. I'm open to PRs that implement it in a clean way, but it isn't a particularly high priority at the moment.

jrochkind commented 7 years ago

I'm also interested in nested functionality -- but I think I'm fine with the "define a custom type" approach, maybe even prefer it, I like the idea of having explicit type classes for the nested objects.

Except I can't quite figure out how to do it. The upgrade guide has an example of the 'old way', and then just directs you to the ActiveRecord attribute class method doc for the 'new way', implying 'figure it out'. I am failing to figure it out from the docs. :)

Can you possibly provide a working example equivalent to or similar to the 'old way' example, but using the ActiveRecord::Type API? It would be super helpful.

jrochkind commented 7 years ago

I'm looking at AR/AS code, and getting some cool ideas. I'm going to try to work something up, maybe as a PR to this. Not sure how long it will take me. :) What you've done here is super powerful, thanks! I think it can be added to. Also considering ways to refactor it to add less of those dynamic methods to the model, and use more of the built-in Rails5 API, like store_accessor and more use of Types, and maybe provide an API that looks more like AR attribute.

This is really cool stuff, thanks so much for sharing, I think maybe it can be even cooler, I'll see how my experiment goes. :)

jrochkind commented 7 years ago

So I started down the path of figuring out how to use nested objects here, and ended up just writing my own thing, deeply inspired by the awesome stuff you've done here, but pretty much rewritten from scratch, with different API and and internal implementation and somewhat different feature set. It's pretty much new code, but I couldn't have done it without seeing what you'd done here first.

Still currently an experimental work in progress.

https://github.com/jrochkind/json_attribute

DavidHooper commented 7 years ago

Looks great @jrochkind, I like that you've used other classes to nest objects.

mckramer commented 7 years ago

This gem and json_attribute are great. I have been looking for a solution to deeply nest objects with proper validation/form support. I have used Mongoid for a while. I have a few use-cases where the Mongo documents are very valuable, but I think I could get by with relational for the majority, if I can reproduce some of the embedded behaviors using the jsonb columns.

I wanted to leave this here in case anyone had some additional thoughts on what embedded/deeply nested objects/collections behaviors might mean for jsonb + ActiveRecord.

jrochkind commented 1 year ago

FWIW, my https://github.com/jrochkind/attr_json (metioned above as json_attribute, an older name) has been working out well, but there are some things it does not as consistently as I'd like with rails attributes.

I come back here to look at jsonb_accessor, and I can learn a lot from it, that I can use. Hooray for open source.

However. Despite the upgrade guide here suggesting:

If you need these sort of methods, you can create your own type class and register it with ActiveRecord::Type

In my attempts, there actually isn't any way to get that to work with jsonb_accessor. At least not with being able to mutate an object in place. After making any changes to a nested object, you would always have to call something like top_model.top_attr = top_model.top_attr to get the changes to be picked up. As suggested in this other issue comment here

But I think that workaround is always necessary, even if you do implement a custom type class suggested in the upgrade guide.

(If the nested objects were immutable, that would work fine, as you'd only be able to change them with the top-level setter!)

If anyone has been able to to get this approach working with jsonb_accessor, I'd love to see it!

(To be clear, my attr_json is working fine, but there are some things jsonb_accessor does better, and I was hoping to make my implementation more like jsonb_accessor's to be able to get the best of both worlds... but found I couldn't, jsonb_accessors implementation doesn't allow mutable nested objects, even with a custom type class -- as far as I can tell! If anyone has solved it, I'd love to hear about it!)