Reputation: 3540
I'm trying to allow for one resource, Site
, to conditionally have a parent resource, Organization
.
Here's what I currently have:
resources :site do
resources :posts do
resources :comments
end
end
this results in paths like
/sites/1
/sites/1/edit
/sites/1/posts
/sites/1/posts/123/comments
# etc. etc.
I want the ability to have paths like
/parent_id/sites/1
/parent_id/sites/1/edit
/parent_id/sites/1/posts
/parent_id/sites/1/posts/123/comments
but only if the Site belongs to an Organization.
I also don't want to have to change every single path helper already in use across my site (there are literally hundreds of places).
Is this possible?
Here's what I've tried:
scope "(:organization_id)/", defaults: { organization_id: nil } do
resources :site
end
# ...
# in application_controller.rb
def default_url_options(options = {})
options.merge(organization_id: @site.organization_id) if @site&.organization_id
end
but that didn't work. organization_id
wasn't getting set.
# in my views
link_to "My Site", site_path(site)
# results in /sites/1 instead of /321/sites/1
I also tried setting the organization_id in a route constraint, and that didn't work as well.
Upvotes: 3
Views: 795
Reputation: 3540
I ended up writing a monkey patch overriding the dynamic method generated for all of my relevant paths. I used a custom route option, infer_organization_from_site
that I look for when dynamically generating routes. If the option is set, I add site.organization
as the first argument to the helper call.
# in config/routes.rb
scope "(:organization_id)", infer_organization_from_site: true do
resources :sites do
resources :posts
# etc.
end
end
# in an initializer in config/initializers/
module ActionDispatch
module Routing
# :stopdoc:
class RouteSet
class NamedRouteCollection
private
# Overridden actionpack-4.2.11/lib/action_dispatch/routing/route_set.rb
# Before this patch, we couldn't add the organization in the URL when
# we used eg. site_path(site) without changing it to site_path(organization, site).
# This patch allows us to keep site_path(site), and along with the proper
# optional parameter in the routes, allows us to not include the organization
# for sites that don't have one.
def define_url_helper(mod, route, name, opts, route_key, url_strategy)
helper = UrlHelper.create(route, opts, route_key, url_strategy)
mod.module_eval do
define_method(name) do |*args|
options = nil
options = args.pop if args.last.is_a? Hash
if opts[:infer_organization_from_site]
args.prepend args.first.try(:organization)
end
helper.call self, args, options
end
end
end
end
end
end
end
Upvotes: 1
Reputation: 5690
You're very close with your current approach. The problem appears to be that defaults
on the routing scope
takes precedence over default_url_options
, so you'll end up with a nil organization ID every time.
Try instead, just:
# in routes.rb
scope "(:organization_id)" do
resources :site
...
end
# in application_controller.rb
def default_url_options(options = {})
# either nil, or a valid organization ID
options.merge(organization_id: @site&.organization_id)
end
Upvotes: 0
Reputation: 1729
Add another block to your routes with the companies resources wrapped around it:
resources :companies do
resources :site do
resources :posts do
resources :comments
end
end
end
resources :site do
resources :posts do
resources :comments
end
end
Now you can create a helper for your links like this:
# sites_helper.rb
module SitesHelper
def link_to_site(text, site)
if site.company
link_to text, company_site_path(site.company, site)
else
link_to text, site_path(site)
end
end
end
Then use it in your view like this:
<%= link_to_site("Text of the link", variable_name) %>
Notice the variable_name
in the arguments. It can be site or @site, depending on your code. Inside a loop it will probably be site but on a show page I guess it will be @site.
Upvotes: 1