-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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 context to Page #2457
base: main
Are you sure you want to change the base?
Add context to Page #2457
Conversation
Is it better to create Administrate::Page::Context because the contents of the context are too free and it's concerning? |
cab44ec
to
793187e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, it's been 4 months 😓 I'm starting to look into this now. I had a first quick look today, but couldn't quite get everything yet. I thought perhaps I can start asking questions and that way I'll force myself to be on top of this, if you are still available?
Thank you for the PR, by the way!
# | ||
# @param resource [ActiveRecord::Base] A resource to be contextualized. | ||
# @return nothing | ||
def contextualize_resource(resource); end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that I understand this method. It's intended to make changes on the resource, but the context seems better suited to live in the Page
object (which has access to the resource anyway). I'm not sure I would want to alter the model in this way.
Would you be able to explain this a bit more? 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your review!! I have been waiting for your response for a long time.
I would like to contextualize the Page
object, but I also intend to contextualize the resource in the Controller. By contextualizing the resource, we can use it to branch validations or trigger callbacks.
I believe that contextualize_resource
should be separate from the Page
's context, as it serves a similar function to scoped_resources
and authorize_resource
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think I see now what is going on 👍
When contextualizing, we can hide fields and then put default values on them. In your example you do this to use the customer
field from non-admins, then set its value to the current user on new/create.
I'm not convinced about the following:
- Doing this as part of
authorize_resource
, which is a separate concern. - Doing this only when
resource.is_a?(ActiveRecord::Base)
, which seems artificial. OK, it's not like we have currently support for anything other than ActiveRecord, but this explicit check seems strange to me. - Having this new API instead of the existing
new_resource
andrequested_resource
instead. It's two separate steps, but it uses existing, known APIs. I have tried locally and it appears to work (I'm having issues running specs so I can't be 100% sure).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your review.
I have separated contextize_resource
from authorize_resource
. As a result, the resource no longer passes as a model class from the index, which also resolves the ActiveRecord::Base
issue.
I believe the current form of requested_resource
is ideal. What do you think? I would like to introduce the same form for new
and create
. Do you have any good suggestions on how to do this?
For example, what do you think about this form?
def built_resource(params: {})
@built_resource = new_resource(params).tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure, since the same effect can be achieved by overriding new_resource
and requested_resource
as mentioned. I have created an experimental branch with your PR that shows an example of this: main...pablobm:use-cases-for-contextualize I'll experiment a bit to see what the possibilities are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shifting slightly to a different topic, I feel that the current action methods in the Administrate's ApplicationController are quite fat, which I believe limits the customizability for us, the application developers.
Therefore, it would be beneficial if these methods were broken down further, allowing us to override specific methods during application development.
I believe it would be ideal if we could proceed with method division and achieve a more unified form as shown below.
(It might be beneficial to also divide page = Administrate::Page::Form.new
and the part where context is injected into Page
into separate methods.)
def new
page = Administrate::Page::Form.new(dashboard, built_resource)
render ...
def edit
page = Administrate::Page::Form.new(dashboard, requested_resource)
render ...
def create
if built_resource(resource_params).save
...
def update
if requested_resource.update(resource_params)
...
def built_resource(params: {})
@built_resource ||= new_resource(params).tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end
end
def requested_resource
@requested_resource ||= find_resource(params[:id]).tap do |resource|
authorize_resource(resource)
contextualize_resource(resource)
end
end
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the other two commits are good ideas. Thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I prepared built_resource
in addition to new_resource
is because I thought it might affect the following process that is used as a helper_method from the view. Do you foresee any issues with this?
https://github.com/search?q=repo%3Athoughtbot%2Fadministrate+new_resource+language%3AHTML%2BERB&type=code&l=HTML%2BERB
resources = apply_collection_includes(resources) | ||
resources = order.apply(resources) | ||
resources = paginate_resources(resources) | ||
page = Administrate::Page::Collection.new(dashboard, order: order) | ||
page.context = self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following on my comment about contextualize_resource
, I wonder if this is what it should be doing instead? Acting on the page instead of the resource here:
page.context = self | |
contextualize_page(page) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the Controller object is almost essential as a Context for the Page, so how about passing it to the initializer? Like the following:
page = Administrate::Page::Collection.new(dashboard, context: self, order: order)
or
page = Administrate::Page::Collection.new(dashboard, controller: self, order: order)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially that makes sense, but then the developers wouldn't be able to alter the context easily with a hook like contextualize_page
. Having said that, perhaps there isn't a use case for that...? I have no idea yet. I'm experimenting now to see the possibilities of your PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had another look 🙂 While we are doing this, would you be able to rebase this in top of master, please? It will help me run the specs, as the way webdrivers are loaded has changed since the PR was created.
# | ||
# @param resource [ActiveRecord::Base] A resource to be contextualized. | ||
# @return nothing | ||
def contextualize_resource(resource); end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I think I see now what is going on 👍
When contextualizing, we can hide fields and then put default values on them. In your example you do this to use the customer
field from non-admins, then set its value to the current user on new/create.
I'm not convinced about the following:
- Doing this as part of
authorize_resource
, which is a separate concern. - Doing this only when
resource.is_a?(ActiveRecord::Base)
, which seems artificial. OK, it's not like we have currently support for anything other than ActiveRecord, but this explicit check seems strange to me. - Having this new API instead of the existing
new_resource
andrequested_resource
instead. It's two separate steps, but it uses existing, known APIs. I have tried locally and it appears to work (I'm having issues running specs so I can't be 100% sure).
4772467
to
83e593d
Compare
As mentioned in comments above, I have created a branch that uses your code and I'm experimenting with it. It's at main...pablobm:use-cases-for-contextualize. My goals are:
|
I think this is the way to go, but there are some things still to polish. Let's see if I can make sense. The main issue is #2363. This lists several other issues that require context. Therefore when we look for a solution we should find something that can tackle all the presented use cases, or at least enable a path towards tackling them in the future. Let's look at them one by one. #1342 How to scope dropdown options to current_user in formsI think this is addressed here, but the issue covers only part of the story. The proposed change tackles #1586 Support for virtual fields (regression)I'm not sure this one is actually related 😅 In any case, if all fields can get a context, then this one will benefit too. #1862 Restricting specific fields visible in show/editThere are several things here:
#1988 Filtering Field::HasManyAlready mentioned. #2060 valid_actions? doesn't work for has_many relationsI think the context on Please let me know what you think! |
While it is possible to use the
I have another suggestion regarding this. In the edit screen, there are cases where users don't have permission to edit certain data but still need to see it. Do you have any ideas for mixing the
Isn't this the same issue as specific fields restrictions? |
I think we are in agreement here:
|
@pablobm Is there anything else I can help with moving forward? If the scope of this PR is decided, I'm ready to commit to it until the end. |
@goosys - I think you are good to go, thank you! 👍 If it helps, you can let me know as you go and I can keep try keep an eye on your pushes to see how you are doing. |
83e593d
to
8ef9389
Compare
@pablobm I've created a PR for virtual fields. Please review it when you have time. |
@pablobm Since field is passed to the scope, the amount of code required to reach the context-aware resource is quite large. scope: -> (field) { field.page.resource.published_entries } I would like to simplify it to something like the following: scope: :published_entries What do you think? |
I’ve been testing this branch in my project, and I’m starting to feel that using only the Page context isn’t enough. Alternatively, I think this approach works fine for the Page. What do you think? |
753e224
to
1d64c08
Compare
I rebased after #2658 was merged. |
98778dc
to
781d936
Compare
781d936
to
7afd445
Compare
I have some proposal regarding Issue #2363.
I believe this PR can still be improved, so if anyone has any opinions, please review and let me know.
Administrate::Page#context
By keeping the context in the Page as follows,
We can now define it as follows in the Dashboard.
With this, we can now differentiate the BelongsTo options based on the current user's permissions.
Also, by using context in form_attributes like this, we can now differentiate form elements based on the current user's permissions.
Administrate::ApplicationController#contextualize_resource
I've prepared a hook point to add context to the resource.
This allows us to automatically set the current_user(pundit_user) to the customer form element that was omitted in the previous section.
Also, we can change the context here for models where the content of the validation changes under certain conditions.
How do you think about?