Add wildcard urls - a proposal#3776
Conversation
e835077 to
7b1fdcb
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3776 +/- ##
==========================================
+ Coverage 98.06% 98.08% +0.02%
==========================================
Files 322 324 +2
Lines 8526 8630 +104
==========================================
+ Hits 8361 8465 +104
Misses 165 165 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
f7b0ca1 to
1846784
Compare
1846784 to
67f57ac
Compare
3039bfa to
dfb07d3
Compare
|
I adjusted the whole structure on this PR and updated the page defintion to use |
060ea55 to
c0a7f60
Compare
644b03a to
ef93e91
Compare
There are already format for email, url, and link_url, but the spec was missing.
Add the configuration for integer and uuid to format matchers. These option will be used for the wildcard url validation.
Add an active record type for wildcard urls. These types will be later used in the page definition and can a simple string or hash with a pattern attribute and an optional params attribute. This way the user can configure the wildcard for a page layout and it will be validated when Alchemy reads in the page layout.
ef93e91 to
dbb9ae9
Compare
|
I renamed the service to |
Add a new attribute to page definition to allow the usage of wildcard_urls (e.g. :user_id/profile). They can have different configurations (simple string or a hash structure). Add also more page layouts to the dummy to test the different configuration later.
Prevent the creation of multiple pages with the same wildcard_url under the same parent page. Alchemy will validate the slug and will prevent creating the same slug twice. Also delegate the wildcard_url to the page delegation.
Add a new service which find the page by urlname or should try to find the correct wildcard url for a given parent_page. It will traverse the page tree and will try to match all page_layouts with a wildcard_url. The first match wins and will be returned. The page and params are stored in the service itself and can be received later in the controller.
Use the page finder service object to find the page per urlname or try to find a page with wildcard_url. The params will be extended by the page finder service, if a wildcard url is used.
It wouldn't work to update the slug anyway, because the urlname mechanic is different for pages with a wildcard_url. The slug form field will be disabled and the slug can also contain slashes.
dbb9ae9 to
22ebb5c
Compare
The urlname getter is resolving the urlname attribute as before, but it has now the ability to substitute wildcard parameter if the urlname has those.
Allow the setting of wildcard parameter in url_path class. It is using the already given optional_params attribute and the previous addition to the urlname method to add parameter to the wildcard url.
| urlname: params[:urlname], | ||
| language_code: params[:locale] || Current.language.code | ||
| ) | ||
| @page ||= PageFinder.new(params: params).call(params[:urlname]) |
There was a problem hiding this comment.
I do not think there is advantage in passing params in two ways to this service
| @page ||= PageFinder.new(params: params).call(params[:urlname]) | |
| @page ||= PageFinder.new(params:).call |
| @params = params | ||
| end | ||
|
|
||
| def call(urlname) |
There was a problem hiding this comment.
We already have the params, we should use the instead of passing a subset into this method.
| def call(urlname) | |
| def call |
| extracted_params = extract_matching_params(candidate_url, pattern, child.wildcard_url.params) | ||
| next unless extracted_params | ||
|
|
||
| @params.merge!(ActionController::Parameters.new(extracted_params).permit(*extracted_params.keys)) |
There was a problem hiding this comment.
We should never merge params like this. We should return new params object instead.
params = ActionController::Parameters.new(extracted_params).permit(*extracted_params.keys)Then we should introduce a PageFinder::Result object which holds the page and the params.
Then the controller will use it
result = PageFinder.new.call(params[:urlname])
@page = result.page
params.merge!(result.params) if result.params.present?| class PageFinder | ||
| attr_reader :params | ||
|
|
||
| def initialize(params: ActionController::Parameters.new) |
There was a problem hiding this comment.
| def initialize(params: ActionController::Parameters.new) | |
| def initialize(urlname:) |
|
Two suggestions for Single-query optimization for Instead of walking the tree level by level (one query per depth level), we could avoid the N+1 with a single query approach:
candidates = Current.language.pages.contentpages.where("urlname LIKE ?", "%:%")
input_segments = urlname.split("/") # ["products", "42", "comments"]
candidates.find do |candidate|
pattern_segments = candidate.urlname.split("/") # ["products", ":id", "comments"]
next unless input_segments.size == pattern_segments.size
# match static segments exactly, extract params from wildcard segments
endSince urlnames are stored with wildcard patterns literally (e.g., For ambiguous matches (multiple candidates with same segment count), we'd need explicit priority — e.g., prefer the most specific match (most static segments). But that's solvable. Validate constraint types in Currently if value.is_a?(Hash) && value[:params].is_a?(Hash)
value[:params].each_value do |constraint|
next unless constraint.is_a?(String)
unless Alchemy.config.format_matchers.respond_to?(constraint.to_sym)
raise ArgumentError, "Unknown format matcher: #{constraint.inspect}"
end
end
endThat way typos blow up at YAML load time, not silently at request time. |
| - name: product_detail | ||
| wildcard_url: | ||
| pattern: ":id" | ||
| params: integer |
| # Returns the urlname of the page. | ||
| # If the page has a wildcard_url, you can pass params to substitute | ||
| # the wildcards in the urlname: page.urlname(id: 42) # => "products/42" | ||
| def urlname(**params) |
There was a problem hiding this comment.
I would prefer to handle this in url_path instead.
What is this pull request for?
Add a possibility to create wildcard pages. A page layout can now have a wildcard_url (e.g. ":awesome_id"). With that in place it is possible to create dynamic pages.
This PR is only the technical groundwork. It does not handle admin view changes. It would be preferable to prevent changing the slug in the page information modal and it would be helpful for the user to see a hint in the page tree, if a page contains wildcards.
Screenshots
Page Layout Configuration
A resolved URL
View Template
Page Sitemap
Page Properties
Checklist