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

Better support for shallow routes #203

Open
brendon opened this issue Jun 17, 2024 · 6 comments
Open

Better support for shallow routes #203

brendon opened this issue Jun 17, 2024 · 6 comments

Comments

@brendon
Copy link

brendon commented Jun 17, 2024

This is just a quick ping to test your appetite for modifications to decent_exposure so that it more smoothly supports shallow routes in Rails.

I've been working around it for many years now with more convoluted exposures but it could be something that the gem supports natively. I'm not sure if you're still using the gem in your own work day to day and if you've come across this yourself?

Primary considerations for shallow routes:

  • The model hierarchy is built backward from the deepest findable resource rather than finding the shallowest and using this to scope the finding of deeper resources.
  • Shallow routes sometimes depend on the parent route (for new, create and index). The best way to detect this is via the presence of the parent_id param. The id param will only ever be present for the child resource.

That's all I can think of off the top of my head, but I just wanted to raise it again in case you're more open to it these days :D

This is an example of my more convoluted exposures:

  expose :form, id: :form_id, build: -> { element.form }
  expose :elements, from: :form
  expose :element, id: :id, model: 'Forms::Element', scope: -> (model) { params[:id] ? model : elements },
    build_params: -> { element_params }
@mattpolito
Copy link
Member

@brendon How would you anticipate it working?

@brendon
Copy link
Author

brendon commented Jun 17, 2024

Without diving too deep I think perhaps a shortcut key (like some of the others) such as shallow: true or shallow: form might be enough. I'd have to actually get into the code to really know for sure :D

@mattpolito
Copy link
Member

I guess I don't understand what you mean by shallow routing then. DecentExposure doesn't correlate to routing at all.

Are you just trying to shortcut a way to auto look at some parent_id?

@brendon
Copy link
Author

brendon commented Jun 18, 2024

Shallow routing in Rails just means that at any one time there's only one id present in the route. For resourceful routes it's either the parent resource id (in the case above :form_id) or just the :id of the resource itself. The parent id is required for index, new, and create, but not for the rest of the routes because they can be found using the id of the resource itself and then backing up the model resource chain (e.g. element.form).

DecentExposure does have some consideration of parent/child resources (e.g. via scopes and some of the other shortcut keys) but I think it makes the assumption that you'd always work your way down from the parent to the child when doing operations on the child (e.g. the edit action would use the paren't scope to find the child being edited, but with shallow routes we can't find the parent without first finding the child because there is no parent id present.

Hope that helps explain it a bit more?

@mattpolito
Copy link
Member

Thanks for the additional context. I'm open to adding features when they make sense. Currently I'm still not understanding the need. If you want to give some examples of the interface you'd like to see and what you'd expect it to do, I'll take a look and try to further understand.

@brendon
Copy link
Author

brendon commented Jun 19, 2024

No worries at all :) I'll have better crack at explaining:

Given a routes setup like this:

resources :lists, shallow: true do
  resources :items
end

We get the following routes:

       Prefix Verb   URI Pattern                         Controller#Action
        lists GET    /lists(.:format)                    lists#index
              POST   /lists(.:format)                    lists#create
     new_list GET    /lists/new(.:format)                lists#new
    edit_list GET    /lists/:id/edit(.:format)           lists#edit
         list GET    /lists/:id(.:format)                lists#show
              PATCH  /lists/:id(.:format)                lists#update
              PUT    /lists/:id(.:format)                lists#update
              DELETE /lists/:id(.:format)                lists#destroy
   list_items GET    /lists/:list_id/items(.:format)     items#index
              POST   /lists/:list_id/items(.:format)     items#create
new_list_item GET    /lists/:list_id/items/new(.:format) items#new
    edit_item GET    /items/:id/edit(.:format)           items#edit
         item GET    /items/:id(.:format)                items#show
              PATCH  /items/:id(.:format)                items#update
              PUT    /items/:id(.:format)                items#update
              DELETE /items/:id(.:format)                items#destroy

In order to expose these relations, currently I need to do the following:

expose :list, id: :list_id, build: -> { item.list }
expose :items, from: :list
expose :item, id: :id, scope: -> (model) { params[:id] ? model : items }

These are the reasons for the overrides:

  • The default for id is a cascade of checks, essentially params[:list_id] || params[:id]. This won't work in our scenario because the list exposure will incorrectly pick up the id param intended to identify the item. We need to hard code the parent id to be list_id and the child id to be just id.
    def params_id_key_candidates
    ["#{model_param_key}_id", "#{name}_id", "id"].uniq
    end
  • We override build so that if list_id doesn't exist, we get it by calling list on the exposed item instead. Otherwise we'll get a new list.
  • By default the scope is just the model class, but in the case of an absent id the scope needs to be the parent collection exposure (items in this case). This allows us to correctly build an item based off a supplied list.

It's not too much to override, but to do it every time gets a bit verbose and there's definitely a pattern there. In terms of an interface, perhaps something like:

expose :list, shallow_child: :item
expose :items, from: :list
expose :item, shallow_parent_scope: :items

Kinda messy naming. I'm sure with some extra thought I could come up with something. Let me know what you think.

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

No branches or pull requests

2 participants