Ignore part of the path in Rails 3 - ruby-on-rails

I would like to be able to ignore part of the paths in my application.
For example:
example.com/products/toys/big-toy, should be routed by ignoring the 'toys' part (just products/big-toy). I am aware of the wildcard symbol available in the routes, but it ignores everything after the products path. I am not sure how to do this and keep my nested resources working.
Routes:
resources :products do
member do
match :details
end
resources :photos
end
product.rb:
def to_param
"#{category.slug}/#{slug}"
end

One way to solve this would be to use a route constraint.
Try this:
resources :products, constraints: { id: /[^\/]+\/[^\/]+/ } do
member do
match :details, via: :get
end
resources :photos
end
This will capture the product :id as anything with a slash in the middle, so /products/abc/xyz/details will route to products#details with params[:id] equal to abc/xyz.
Then, you could add a before filter in your ProductsController, like this:
class ProductsController < ApplicationController
before_filter :parse_id
// ...
def parse_id
slugs = params[:id].split("/")
params[:category_id] = slugs[0]
params[:id] = slugs[1]
end
end

Related

Rails Routing Precedence favors the first

So Ive been working on a rails project that defines two different create actions in the same controller. Here's my controller:
class SmsSendsController < ApplicationController
def new
#at = SmsSend.new
#contact = Contact.find_by(id: params[:id])
end
def create
#at = SmsSend.create(sms_params)
if #at.save!
#con = current_user.contacts.find_by(id: #at.contact_id)
AfricasTalkingGateway.new("trial-error").sendMessage(#con.phonenumber, #at.message)
end
end
def new_all
#at = SmsSend.new
#contact = Contact.find_by(id: params[:id])
end
def create_all
#at = SmsSend.create(sms_params)
if #at.save!
current_user.contacts.each do |c|
AfricasTalkingGateway.new("trial-error").sendMessage(c.phonenumber, #at.message)
end
end
end
private
def sms_params
params.require(:sms_send).permit(:mobile, :message, :contact_id)
end
end
In my
routes.rb
file, Ive used both custom and resourceful routes to define routes for the first and the second new/create actions:
Rails.application.routes.draw do
devise_for :users
get 'sms_sends/new_all', to: 'sms_sends#new_all'
post 'sms_sends', to: 'sms_sends#create_all'
resources :contacts
resources :sms_sends
root 'contacts#index'
end
So both post actions will work if and only if its routes are placed before the other. Is there a way I can get rid of the precedence? Or where am I going wrong?
Thankie.
So both post actions will work if and only if its routes are placed
before the other.
That is how you should define for the routes to work. Because the routes that defined in the routes.rb will be compiled from top-to-bottom. So if your custom routes gets preceded by resourceful routes, then the custom routes will conflict with your resourceful routes.
Is there a way I can get rid of the precedence?
Define them as collection routes like so,
resources :sms_sends do
get 'sms_sends/new_all', to: 'sms_sends#new_all', on: :collection
post 'sms_sends', to: 'sms_sends#create_all', on: :collection
end
The above will generate routes with path helpers like below
sms_sends_new_all_sms_sends GET /sms_sends/sms_sends/new_all(.:format) sms_sends#new_all
sms_sends_sms_sends POST /sms_sends/sms_sends(.:format) sms_sends#create_all
For a better readability, you can change your custom routes like so
resources :sms_sends do
get 'new_all', to: 'sms_sends#new_all', on: :collection
post 'create_all', to: 'sms_sends#create_all', on: :collection
end
This will generate the path helpers like below
new_all_sms_sends GET /sms_sends/new_all(.:format) sms_sends#new_all
create_all_sms_sends POST /sms_sends/create_all(.:format) sms_sends#create_all

Rails query parameter with same name as optional path parameter

Suppose, I have this code in routes.rb
get "/items/(:brand)", to: "items#index", as: :items
I can't change this route, because sometimes I need urls with brand in path (not in query).
Can I create path like this:
/items?brand=TestBrand
but not this:
/items/TestBrand
via route helpers?
items_path(brand: "TestBrand") give 2'nd variant.
This is not a very good solution as it violates the RESTful conventions.
In Rails flavor REST GET /resource_name/:id maps to the show route. In the case of get "/items/(:brand)", to: "items#index", as: :items this creates an ambiguity (is the segment an item id or a brand?) when the router matches the requests and the first declared route will win which is hardly desirable.
A better solution is to declare it as a nested resource:
resources :brands, only: [] do
resources :items, only: [:index], module: :brands
end
Prefix Verb URI Pattern Controller#Action
brand_items GET /brands/:brand_id/items(.:format) brands/items#index
# app/controllers/brands/items_controller.rb
module Brands
class ItemsController < ::ApplicationController
before_action :set_brand
# GET /brands/:brand_id/items
def index
#items = #brand.items
end
def set_brand
#brand = Brand.find(params[:brand_id])
end
end
end
Additional parameters in "get" are supported by default, so maybe you could use
get "/items", to: "items#index", as: :items
or
resources :items, only: [:index]
And use path helper which you provided:
items_path(brand: "TestBrand")
To answer your question - Yes, you can
get "/items", to: "items#index", as: :items
and the following route helper will create
items_path(brand: "TestBrand")
#=> items?brand=TestBrand
NOTE:
If you are using:
recourses :items
You must alredy have this

I'm attempting to only nest resources 1 level deep. How do I make a controller's index defn work for both routes?

I've got this working for the most part, but am getting stuck in one area.
So I've got this defined...
resources :rooms do
resources :visits, shallow: true
end
#resources :visits
Works great. I can get rooms/3/visits just like I want.
My visits_controller has...
def index
#visits = Room.find(params[:room_id]).visits.order('date')
respond_with({visits: #visits}.as_json)
end
This is fine. However... if I want to be able to view all the visits... a la /visits, then my controller definition is going to break this, because the index definition wants a :room_id.
How do you define a controller's index to work both ways, both with an independent visit route and with a nested-resource route of one specific room?
The best way to to probably brute force it:
def index
#visits = if params[:room_id]
Room.find(params[:room_id]).visits
else
Visit.all
end.order('date')
respond_with({visits: #visits}.as_json)
end
If you wanted to tidy this up a little bit
def index
#visits = collection.order('date')
respond_with({visits: #visits}.as_json)
end
def collection
if params[:room_id]
Room.find(params[:room_id]).visits
else
Visit.all
end
end
Then your routes would be:
resources :rooms do
resources :visits, shallow: true
end
resources :visits
You can create a named route for just visits#index action like this:
get '/visits', to: 'visits#index'
resources :rooms do
resources :visits, shallow: true
end
Now you have two routes pointing to visits#index. One that's nested inside :rooms and one that's independent: /visits

rails custom rest route with parameter

I have a questions controller and an associated model and a number of rest routes. Here is how it's set up in routes.rb:
resources :questions
I want to add a custom route that has the format /questions/widget/ID (where ID is the id of the question for which I want to generate a widget). I want this to be processed by the "widget" action in my questions controller. I've tried a number of things such as:
resources :questions do
member do
get 'widget/:id'
end
end
But nothing is working. I'm sure I'm missing something simple. Any ideas? Thanks in advance.
You do not have to specify the id since you are inside resources. It should look like:
resources :questions do
member do
get 'widget'
end
end
You can get more information from the Rails Guide. Look at section 2.9.1.
Edit: I just noticed that you are trying to match get /questions/widget/:id. This will set up a route for get /questions/:id/widget. This is more in line with Rails convention. If you really want it the other way, you need to set up a custom match statement:
match "/questions/widget/:id" => "questions#widget"
However, I would stick with convention.
I know it is old, but looking to fix another routing problem I ended here, it is possible, to do what you are asking for, here is an example
resources :articles do
get 'by_tag/:tag' => :by_tag, on: :collection
get 'by_author/:author' => :by_author, on: :collection
resources :comments, except: :show
end
now you have /artices/by_tag/:tag . The trick was to use on:collection.
Obviously don't forget to add the by_tag action and by_author.
class ArticlesController < ApplicationController
.....
def by_tag
...
end
end
Check this route works with
melardev#local~$ rails routes
Why don't you use this routes:
resources :questions do
resources :widgets
end
it will create path like questions/:question_id/widgets/new for you to create new widget for question with specific id of question.
This is what ended up working for me:
resources :post do
get "author/:author", to: "posts#author", on: :collection, as: "author"
end
Which outputs the following route:
author_posts GET /posts/author/:author(.:format) posts#author
Then in your controller, you need to create the author action:
class PostsController < ApplicationController
def author
#roles = Post.where(author: params[:author])
render :index # to reuse the index view
end
end
Then in your view:
<%= link_to post.author, author_posts_path(post.author), data: { turbo_frame: "_top" } %>

Rails 3 add GET action to RESTful controller

I have a controller with the 7 RESTful actions plus an additional 'current' action, which returns the first active foo record:
class FooController < ApplicationController
def current
#user = User.find(params[:user_id])
#foo = #user.foos.where(:active => true).first
#use the Show View
respond_to do |format|
format.html { render :template => '/foos/show' }
end
end
#RESTful actions
...
end
The Foo Model :belongs_to the User Model and the User Model :has_many Foos.
If I structure the routes as such:
resources :users do
resources :foos do
member do
get :current
end
end
end
The resulting route is '/users/:user_id/foos/:id'. I don't want to specify the foo :id, obviously.
I've also tried:
map.current_user_foo '/users/:user_id/current_foo', :controller => 'foos', :action => 'current'
resources :users do
resources :foos
end
The resulting route is more like I would expect: '/users/:user_id/current_foo'.
When I try to use this route, I get an error that reads:
ActiveRecord::RecordNotFound in FoosController#current
Couldn't find Foo without an ID
edit
When I move the current action to the application controller, everything works as expected. The named route must be conflicting with the resource routing.
/edit
What am I missing? Is there a better approach for the routing?
I think you want to define current on the collection, not the member (the member is what is adding the :id).
try this.
resources :users do
resources :foos do
collection do
get :current
end
end
end
Which should give you a route like this:
current_user_foos GET /users/:user_id/foos/current(.:format) {:controller=>"foos", :action=>"current"}
Also map isn't used anymore in the RC, it will give you a deprecation warning.

Resources