Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reflection on views, fields, and associations #357

Merged
merged 9 commits into from
Jan 9, 2024
Merged

Add reflection on views, fields, and associations #357

merged 9 commits into from
Jan 9, 2024

Conversation

jhollinger
Copy link
Contributor

@jhollinger jhollinger commented Nov 9, 2023

Initial attempt at a reflection API per #341. I've built a POC ActiveRecord automagic preloader against this and #358.

Blueprinter's native views and associations have been wrapped in simpler structs or classes, as the native ones made it pretty hard to get at stuff. Could also protect against internal changes in the future.

I exposed the internal method and name as name and display_name respectively, since Rubocop (and best practices) don't allow methods named "method".

# Returns Hash of views keyed by name
views = WidgetBlueprint.reflections

# Returns Hash of non-association fields keyed by method name
fields = views[:default].fields
fields[:name].name
=> :name
fields[:name].display_name
=> :name

# Returns Hash of associations keyed by method name
assoc = views[:default].associations
assoc[:category].name
=> :category
assoc[:category].display_name
=> :category
assoc[:category].blueprint
=> CategoryBlueprint
assoc[:category].view
=> :default

TODO

  • Update documentation if approach is approved
  • Is there any missing info we feel is critical to expose for initial release?

@ritikesh
Copy link
Collaborator

can you pls share your POC here as well for better understanding on how both of these could be stitched together?

Initial attempt at a reflection API per #341. I've built a POC ActiveRecord automagic preloader against this and #358.

@jhollinger
Copy link
Contributor Author

@ritikesh I hesitate to share the code as it's in a private, company repo atm. I do plan to open source it into procore-oss once all this is merged and I get company approval.

The skeleton is in #358's description, but the magic is mostly in figure_out_preloads. In short it builds up a nested Hash for preload/includes by finding matching associations between the Blueprinter and ActiveRecord classes, recursively. This PR allows the extension to inspect the Blueprinter associations, while #358 allows the extension to tack the preloads onto the ActiveRecord::Relation that was passed to render.

@ritikesh
Copy link
Collaborator

@jhollinger would you be able mimic that with a raw new rails demo app with a few models like posts/users/comments etc? Would just help visualise better is all :)

@jhollinger
Copy link
Contributor Author

jhollinger commented Nov 21, 2023

@ritikesh Here's a gist with the relevant bits https://gist.github.com/jhollinger/9a9056b87c3bef1cb13c1032d3f3bf89. A trivial example of course. Real-world apps that benefit would have much broader and more deeply nested associations.

@ritikesh
Copy link
Collaborator

thanks @jhollinger, this helps. one final question - the preload_blueprint is a custom patch/extension and not something that blueprinter will provide OOTB?

@jhollinger
Copy link
Contributor Author

@ritikesh Correct, that's an ActiveRecord patch the gem with the extension will make.

@ritikesh
Copy link
Collaborator

LGTM then.

Copy link
Contributor

@lessthanjacob lessthanjacob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on this! Left some comments, but overall the direction LGTM!

lib/blueprinter/reflection.rb Outdated Show resolved Hide resolved
lib/blueprinter/reflection.rb Outdated Show resolved Hide resolved
lib/blueprinter/reflection.rb Outdated Show resolved Hide resolved
lib/blueprinter/reflection.rb Outdated Show resolved Hide resolved
lib/blueprinter/reflection.rb Show resolved Hide resolved
Comment on lines 77 to 80
@view.included_view_names.reduce({}) do |acc, view_name|
view = @blueprint.views.fetch(view_name)
acc.merge view.send(type)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me wonder if we could handle this higher level in Reflection, and avoid calling back up it from this class. In other words, can we pass in dependent Reflection::View instances on initialization.

Of course, this introduces an order dependency, which I don't believe we currently enforce.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the concern that we're calling methods on Blueprinter::View here? I guess we could pass view.name, view.fields, view.included_view_names to the constructor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more the circular dependency we're introducing here (e.g. Blueprinter#reflections aggregates Reflection::View objects, which can then call back to Blueprinter#reflections). Happy to consider this a potential follow up, though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose if we initialized Reflection::Views "bottom up" (leaf views first), that could work. I'll think on it a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lessthanjacob I was able to re-use ViewCollection#fields_for in Reflection::View. It's quite a bit cleaner and should be easier to maintain as features are changed/added.

jhollinger and others added 3 commits November 27, 2023 10:54
Co-authored-by: Jake Sheehy <[email protected]>
Signed-off-by: Jordan Hollinger <[email protected]>
Signed-off-by: Jordan Hollinger <[email protected]>
Copy link
Collaborator

@ritikesh ritikesh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these seem like big changes, can you pls update version and add a changelog entry as well?

@lessthanjacob
Copy link
Contributor

@ritikesh We've previously been releasing independently of any particular PR (at least for the past few versions). We can discuss further in Discord if we'd like to change this, though!

@lessthanjacob lessthanjacob merged commit 2060410 into procore-oss:main Jan 9, 2024
5 checks passed
@peterkarman1
Copy link

@ritikesh Here's a gist with the relevant bits https://gist.github.com/jhollinger/9a9056b87c3bef1cb13c1032d3f3bf89. A trivial example of course. Real-world apps that benefit would have much broader and more deeply nested associations.

Would love to see this gist again!

@jhollinger
Copy link
Contributor Author

@peterkarman1 Sorry, I was cleaning out some old gists. The docs for reflection & extensions are in #379. And watch this space: there may be some extensions in the works...

@jhollinger
Copy link
Contributor Author

@peterkarman1 Here's the extension that came from that gist https://github.com/procore-oss/blueprinter-activerecord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants