procore-oss/blueprinter

[Question/Support] Is there anyway to access controller methods (i.e. current_user) in blueprints?

dylandamsma opened this issue · 3 comments

Is there an existing issue for this?

  • I have searched the existing issues

Describe your question or ask for support

I have a MatchBlueprint that is being used heavily across my other blueprints and APIs.
The MatchBlueprint contains a custom field:

field :high_fives do |match, options|
    {
      high_fives_count: match.high_fives_count,
      high_fived: match.high_fived_by?(current_user)
    }
  end

However, current_user is not available to the blueprint, even though it is in the controller the blueprint is rendered from. I don't want to pass it as an option, because then I need to pass it in many places. Is there anyway to set this globally or access the controller method somehow?

I kind of expected it to work like a view, as: "It heavily relies on the idea of views which, similar to Rails views, are ways of predefining output for data in different contexts."

👋 Hey @dylandamsma!

If you need to provide additional context to a Blueprint, I would suggest providing it via the options argument on render. If it's always needed, and you would like to avoid passing it in everywhere, then you could also consider one of:

  1. Decorating the Match object before rendering, so that you can simply call match.high_fived in the Blueprint without needing the current_user context.
  2. Storing and exposing current_user on a per-request basis via a thread-safe store (e.g. ActiveSupport::CurrentAttributes or RequestStore), and invoke that within the Blueprint.

I kind of expected it to work like a view, as: "It heavily relies on the idea of views which, similar to Rails views, are ways of predefining output for data in different contexts."

That's a fair assumption. We may want to rephrase this in the README though, as there's intentionally no "Rails magic" at play here; it's like a view in that it's responsible for presentation/serialization of data, but not specifically analogous to how ERB views work in Rails.

Hi @lessthanjacob

Thank you for the quick response and, let me say it now, because I forgot to mention it earlier: Great work on Blueprinter and I really like this approach. Especially the ability to define views and therefore be more efficient in the data I render to the API.

In the end, I solved it by using a Current.rb model that has the user attribute. Seems that can be accessed in a blueprint, so that did the trick. Now I can call Current.user within:

field :high_fives do |match, options|
    {
      high_fives_count: match.high_fives_count,
      has_high_fived: match.high_fived_by?(Current.user)
    }
  end

The reason I mentioned the views, is because I am used to JB / JBuilder gems, which function similar to erb views.

Hi @lessthanjacob

Thank you for the quick response and, let me say it now, because I forgot to mention it earlier: Great work on Blueprinter and I really like this approach. Especially the ability to define views and therefore be more efficient in the data I render to the API.

In the end, I solved it by using a Current.rb model that has the user attribute. Seems that can be accessed in a blueprint, so that did the trick. Now I can call Current.user within:

field :high_fives do |match, options|
    {
      high_fives_count: match.high_fives_count,
      has_high_fived: match.high_fived_by?(Current.user)
    }
  end

The reason I mentioned the views, is because I am used to JB / JBuilder gems, which function similar to erb views.

It's possible to use ERB views to render blueprints if you want. See here #397

Also like lessthanjacob said, you can also provide your current_user like this

MyBlueprint.render_as_hash(record, user: current_user)

then in your fields

field :high_fives do |match, options|
  options[:user] 
  => user
end