Skip to content

Add reflection on views, fields, and associations#357

Merged
lessthanjacob merged 9 commits intoprocore-oss:mainfrom
jhollinger:reflection
Jan 9, 2024
Merged

Add reflection on views, fields, and associations#357
lessthanjacob merged 9 commits intoprocore-oss:mainfrom
jhollinger:reflection

Conversation

@jhollinger
Copy link
Copy Markdown
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?

@jhollinger jhollinger marked this pull request as ready for review November 9, 2023 14:42
@jhollinger jhollinger requested review from a team and ritikesh as code owners November 9, 2023 14:42
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
@ritikesh
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

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

@ritikesh
Copy link
Copy Markdown
Collaborator

LGTM then.

Copy link
Copy Markdown
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!

Comment thread lib/blueprinter/reflection.rb Outdated
Comment thread lib/blueprinter/reflection.rb Outdated
Comment thread lib/blueprinter/reflection.rb Outdated
Comment thread lib/blueprinter/reflection.rb Outdated
Comment thread lib/blueprinter/reflection.rb
Comment thread lib/blueprinter/reflection.rb Outdated
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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 <jacobjsheehy@gmail.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@gmail.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
Signed-off-by: Jordan Hollinger <jordan.hollinger@procore.com>
Copy link
Copy Markdown
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
Copy Markdown
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
@peterkarman1
Copy link
Copy Markdown

@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
Copy Markdown
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
Copy Markdown
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