decent_exposure
decent_exposure copied to clipboard
Better support for shallow routes
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_idparam. Theidparam 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 }
@brendon How would you anticipate it working?
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
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?
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?
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.
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
idis a cascade of checks, essentiallyparams[:list_id] || params[:id]. This won't work in our scenario because the list exposure will incorrectly pick up theidparam intended to identify the item. We need to hard code the parent id to belist_idand the child id to be justid. https://github.com/hashrocket/decent_exposure/blob/1d64ef2238d3eb6cf73c3887eb4398e94a2a1fd8/lib/decent_exposure/behavior.rb#L88-L90 - We override
buildso that iflist_iddoesn't exist, we get it by callingliston the exposediteminstead. Otherwise we'll get a new list. - By default the
scopeis just the model class, but in the case of an absentidthe scope needs to be the parent collection exposure (itemsin 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.