Rails not differentiating between two resources nested in the same namespaces - ruby-on-rails

I have two types of products that are nested under the same categories. The routes I have setup are
resources :categories, path: '/', only: [:show] do
resources :subcategories, path: '/', only: [:show] do
resources :amazon_products, path: '/', only: [:show]
resources :other_products, path: '/', only: [:show]
end
end
which I was previously accessing using this link
<%= link_to "View Product Page", [product.collection, product.category, product.subcategory, product], class: 'product__link' %>
which that resulted in something like this url after friendly_id finished with it
/cleansers/face-wash-and-cleansers/blemish-remedy-acne-treatment-gelee-cleanser
The issue is that the link only resolves for amazon_products and I'm not sure how to make it differentiate between the two. I think the problem is in the way I am referencing the path since when I enter rails routes in the console, I can see the two different paths there like so
category_subcategory_amazon_product
GET :category_id/:subcategory_id/:id(.:format)
amazon_products#show
category_subcategory_other_product
GET /:collection_id/:category_id/:subcategory_id/:id(.:format)
other_products#show
I tried specifically referencing the other product path using the link
category_subcategory_other_product_path(product.category, product.subcategory, product)
but it is giving me an ActiveRecord::RecordNotFound since its still looking in the wrong controller
app/controllers/amazon_products_controller.rb:5:in `show'
How do I tell rails to differentiate between these two resources?

When you boil it down, Rails routes are relatively simple patterns that match URIs and dispatch requests to the proper controller/method.
If you had specified your nested routes in their basic form:
resources :categories, only: [:show] do
resources :subcategories, only: [:show] do
resources :amazon_products, only: [:show]
resources :other_products, only: [:show]
end
end
The resulting URI patterns for the product level would look like:
/categories/<id>/subcategories/<id>/amazon_products/<id>
/categories/<id>/subcategories/<id>/other_products/<id>
Admittedly verbose, but obviously distinct, patterns.
The issue is that you are using path: '/' on the resources to remove everything unique about the resource routes. So, your product-level route patterns are:
category_subcategory_amazon_product: /<id>/<id>/<id>
category_subcategory_other_product: /<id>/<id>/<id>
Since the two patterns are identical, Rails falls back to the time honoured practice of matching the first one defined. You can demonstrate that for yourself by swapping :other_products so it is first; its controller will then be the one always invoked.
NB: It doesn't matter that you are using friendly-id -- that just changes what the IDs look like to the user, not the basic route pattern.
The solution is to simply reintroduce some uniqueness, at least at the product level.
resources :categories, path: '/', only: [:show] do
resources :subcategories, path: '/', only: [:show] do
resources :amazon_products, path: 'amazon', only: [:show]
resources :other_products, path: 'other', only: [:show]
end
end
The result will be route patterns that Rails can actually distinguish:
category_subcategory_amazon_product: /<id>/<id>/amazon/<id>
category_subcategory_other_product: /<id>/<id>/other/<id>

Related

Rails 6 routes - proper way of simple nesting routes

Because it's been a while since I was using Rails monolith instead of GrapeAPI I've silly question. I want to create a route for path - users/portfolios/1/portfolio_reports/archived_reports where I will displays PortfolioReports.where(status: 'archived'). I created routes:
namespace :users do
resources :portfolios, only: [:index, :show] do
resources :archived_report, only: [:index, :show]
resources :portfolio_report, only: [:index, :show]
end
end
So I've got two questions: Should the routes file look like my current routes.rb ? and if I have Portfolio and PortfolioReport models like below, the portfolio_reports_controller should be inside app/controllers/users/portfolio_reports_controller.rb or app/controllers/portfolio_reports_controller.rb ?
class Portfolio
has_many :portfolio_reports
end
class PortfolioReport
belongs_to :portfolio
end
In Rails you can use "Shallow Nesting" which basically says to only nest the index, new and create actions under the parent resource. For the other actions you don't need to nest the routes, because through the record itself you have access to the associated record, so there is no need to have the id in the url.
So your routes will be:
users/portfolios/ # Portfolios#index
users/portfolios/1 # Portfolios#show
users/portfolios/1/portfolio_reports # PortfolioReports#index
users/portfolio_reports/1 # PortfolioReports#show
users/portfolios/1/archived_reports # ArchivedReports#index
users/archived_reports/1 # ArchivedReports#show
And routes.rb should look like this:
namespace :users do
resources :portfolios, only: [:index, :show] do
resources :archived_report, only: [:index]
resources :portfolio_report, only: [:index]
end
resources :archived_report, only: [:show]
resources :portfolio_report, only: [:show]
end
(If you'd use all 7 routes you could use the helper shallow as mentioned in the docs).
No need to nest archived_reports under portfolio_reports like you mentioned in your question!
Find more info on shallow nesting here: https://guides.rubyonrails.org/routing.html#shallow-nesting
For the user namespace:
Your controllers should live in a subfolder user because you have the namespace user:
app/controllers/user/portfolio_reports_controller.rb

How to use named routes when using FriendlyId at the root path in Rails

I have a basic Page model in Rails that I'm using with FriendlyId to allow admins to create pages like "/about" or "/contact".
I have the following in my routes file (config/routes.rb) to ensure that the slugs for each page appear at the root of the site, such as https://example.com/about, etc:
resources :pages, except: [:show]
resources :pages, only: [:show], path: "/"
The problem is, with this approach, I can't use the normal named route like page_path(#page) in my views(or tests or controllers for that matter) because that routes to "/pages/about" and I get a "No route matches [GET] pages/about" error.
I could do both routes in my routes file so that "/pages/about" and "/about" work like this:
resources :pages
resources :pages, only: [:show], path: "/"
But, that creates an SEO duplicate content problem. I suppose I could create a helper that sets the rel="canonical" url for each page in the html header, but that also feels like a hack. I'd prefer for there to just be 1 version of each page at the root and also have a named route such as "page_path" or "root_page_path" that I can use throughout my app.
The hack I've come up with for the time being is <%= link_to "#{page.slug}" %>, but not having a named route seems very brittle.
What is a more "correct" way to do this in Rails?
I expected something like this to work:
resources :pages, only: [:show], path: "/", as: "page"
But that doesn't work either. Nothing in the Rails guide on routing is really helping either.
You need top switch the order of their definitions:
resources :pages, only: [:show], path: "/"
resources :pages, except: [:show]
resources only give name to the first path with given url. However - you will have the problem now as the pages/:id path (for delete and update) has now no route helper (as it is normally the same as show).
EDIT: As mentioned in the comment - it will also automatically match /pages path to a show action with id equal to pages - not a great idea! Which leads to better option:
resources :pages, except: [:show]
get :id, to: "pages#show", as: :root_page
Which gives you root_page_path(#page) helper for :show action and page_path(#page) for :update and :delete

configuring routes for collections in rails

I have a rails application that needs to search two different collections independently, but both are indexed in a single solr instance. However, when i try to search in one collection, when i get to the item i want displayed, it reroutes to the same id within the other collection. How should i adjust my routes.rb to remedy this?
Here is the code:
Rails.application.routes.draw do
get 'biofile/search'
get 'masterfile/search'
mount Blacklight::Engine => '/'
Blacklight::Marc.add_routes(self)
root to: "catalog#index"
concern :searchable, Blacklight::Routes::Searchable.new
resource :catalog, only: [:index], as: 'catalog', path: '/', controller: 'catalog' do
concerns :searchable
end
resource :masterfile, only: [:search], as: 'masterfile', path: '/masterfile', controller: 'masterfile' do
concerns :searchable
end
resource :biofile, only: [:search], as: 'biofile', path: '/biofile', controller: 'biofile' do
concerns :searchable
end
devise_for :users
concern :exportable, Blacklight::Routes::Exportable.new
resources :solr_documents, only: [:show], path: '/catalog', controller: 'catalog' do
concerns :exportable
end
resources :solr_documents, only: [:show], path: 'masterfile', controller: 'masterfile' do
concerns :exportable
end
resources :solr_documents, only: [:show], path: 'biofile', controller: 'biofile' do
concerns :exportable
end
resources :bookmarks do
concerns :exportable
collection do
delete 'clear'
end
end
get '/catalog/masterfile/', to: 'masterfile#search', as: 'masterfile'
get '/catalog/biofile/', to: 'biofile#search', as: 'biofile'
get '*path' => redirect('/')
end
are you asking about nested routes? you want the path to look like model1_model2_path? the below example will give you group_memberships_path to use in your view and the url would have the id of the group and the id of the membership like .../groups/22/membership/13/edit
use rails routes in rails 5.x or rake routes in rails 4 to see the list of your new routes and you can see the urls will take 2 id's that can be passed to your application.
# nested routes for groups
resources :groups do
resources :memberships
end
I think you'll need to override this method https://github.com/projectblacklight/blacklight/blob/9f676994f437f7664cfc2b5c4dffe4382a6d14d3/lib/blacklight/search_state.rb#L48 either by configuration or monkeypatch to behave differently based on some info in solr document

How to properly add single route to existing route resources

I want to properly add new route to already existing route resources in Spree.
Desired end url format: /orders/:order_id/order_returns(.:format)
I can achieve this by adding following snippet to routes.rb:
Rails.application.routes.draw do
resources :orders, only: [] do
resources :order_returns, controller: 'order_returns', only: [:create]
end
end
but resources :orders, only: [] do looks kind of ugly with empty hash and if I remove it Rails will generate routes for orders :(
Is there a better, Rails/Spree way to achieve that ?

Custom rails routing helper method

I am having issues getting my helper method names to work properly, any suggestions would be great:
#config/routes.rb
resources :junkie, only: [:show, :index, :destroy], as: :junkie do
get :merge, on: :collection
end
So I was having issues because I the singular form of junkies is junky, but when I make this change and look at the routes it changes the #merge helper to:
merge_junkie_index GET /junkies/merge(.:format) junkies#merge
Is there any way to change this to just merge_junkie? I tried removing it from the resource black and using the match syntax: get "junkies/merge" => "junkies#merge", as: :junkie but for some odd reason this directed me to the show method even though the route was right.
The solution is a ugly one but it works, since the show route is the only one that is affected by the as: :junkie you can break it out put the merge route in a separate block. The ordering of the resource also matters for some reason, if you do not put the merge first, it will interpret the url /junkie/merge/ as a id and hit the show action. So it should look like this in your routes file:
resources :junkies, only: [:index] do
get :merge, on: :collection
end
resources :junkies, only: [:show, :destroy], as: :junkie

Resources