- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.1k
Add context to Page #2457
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
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!
| authorize_scope(scoped_resource).find(param) | ||
| end | ||
|  | ||
| def authorize_scope(scope) | 
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.
Perhaps this introduction of authorize_scope is off-topic in this PR? I can see the value of separating this concern, but it's different from the goal of adding context to the pages. 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.
Yes, you're right, this change is off-topic. I appreciate your accurate understanding of my intention.
However, I thought this change was also necessary for effective use of the contextualize_resource method, so I included it in the same PR. If necessary, I can split the 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.
Unresolving this as I think it's not settled. Currently I think this should be separated into a different PR. Perhaps in the context of your work on #2814
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’ve removed authorize_scope from this PR.
I’m planning to revisit it either in a separate PR or during a future refactor.
| # | ||
| # @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_resourceandrequested_resourceinstead. 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
endThere 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
    endWhat 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 = 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_resourceandrequested_resourceinstead. 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_entriesWhat 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. | 
781d936    to
    7afd445      
    Compare
  
    7afd445    to
    b449732      
    Compare
  
    | I have rebased again. @pablobm 
 As a result, I believe the status of the issues in #2363 is as follows. 
 For #1862 and #2060, we came up with new ideas during discussions. | 
| After this, I still have a few tasks I’d like to work on: 
 | 
b449732    to
    0c5e538      
    Compare
  
    88e5ecb    to
    a3ecb08      
    Compare
  
    a3ecb08    to
    011f06c      
    Compare
  
    | @nickcharlton | 
| I haven't been following this one because @pablobm has, but I just gave it a bit of a read to understand where we are and what we're trying to do and I think this is overall good. I'm also keen on your further ideas as well. I'm really trying to keep the remaining bits of v1 focused on final testing before we cut the release, so I'm going to tag it post-v1. As for keeping your branches up to date (as you've been rebasing them a lot and I know how much of a pain it is!) I wouldn't expect much more to go in. | 
afbd64b    to
    1be075d      
    Compare
  
    66be5f3    to
    b559109      
    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?