Rails Routing Precedence favors the first - ruby-on-rails

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

Related

How to setup Rails routes.rb for Vanity URL without requiring a prefix

I'm looking to setup routes.rb to support vanity URLs that do not require a prefix. For example not requiring the "articles/" in mysite.com/articles/seo-url-here. I want just mysite.com/seo-url-here
How can I setup routes.rb so that when a url hits my site: routes.rb looks to see if the value of seo-url-here in the url matches a record in my database in the table Article.seo_url. If not match is found, then routes.rb should move on down through the rest of the routes.rb file.
Basic code to get you started:
# config/routes.rb
Rails.application.routes.draw do
resources :posts
resources :authors
constraints(PostUrlConstrainer.new) do
get "/:id", to: "posts#show"
end
end
# app/constraints/post_url_constrainer.rb
class PostUrlConstrainer
def matches?(request)
title = request.path_parameters[:id]
Post.find_by(title: title)
end
end
# app/controllers/posts_controller.rb
def set_post
#post = Post.find_by(title: params[:id])
end
Related article: Pretty, short urls for every route in your Rails app - Arkency Blog
Searching for rails url constraint seems to help.

Routing error for rails - uninitialized constant SubscribersController

I have a Subscriber model that takes in a "phone_number" and a "visit" integer. I have two controllers Subscribers and Visits(super and sub) I have never worked with nested controllers before and I'm having some issues with namespace I believe. Because I getting back the uninitialized constant error. Basically the subscriber controller signs up a subscriber and the visit controller counts the amount of times they've visited by user input of their phone_number. Why am I getting this error? I'll show my code for clarity.
CONTROLLERS
class Subscribers::VisitsController < ApplicationController
def new
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.find_by_phone_number(params[:phone_number])
if #subscriber
#subscriber.visit += 1
#subscriber.save
redirect_to subscribers_visits_new_path(:subscriber)
else
render "new"
end
end
end
class SubscribersController < ApplicationController
def index
#subscriber = Subscriber.all
end
def new
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.create(subscriber_params)
if #subscriber.save
flash[:success] = "Subscriber Has Been successfully Created"
redirect_to new_subscriber_path(:subscriber)
else
render "new"
end
end
ROUTES
Rails.application.routes.draw do
devise_for :users
resources :subscribers, except: :show
get '/subscribers/visits/new', to: 'subscribers/visits#new'
root "welcomes#index"
VIEWS
<h1>hey</hey>
<%= form_for #subscriber do |form| %>
<div class="form-group">
<p>
<%= form.label :phone_number %>
<%= form.text_field :phone_number %>
</p>
<% end %>
ERROR
Hmm, my guess is you are trying to route url subscriber/visits/new to new action in VisitsController?How about changing this line:
get '/subscribers/visits/new', to: 'subscribers/visits#new'
to:
namespace :subscribers do
get '/visits/new', to: 'visits#new'
end
Also try to move this block above resources :subscribers, except: :show if you still get the error.
Cheers
You probably do not need to inherit one controller from another. Simply define the controllers as you normally would:
app/controllers/subscribers_controller.rb
class SubscribersController < ApplicationController
# methods for Subscribers
end
in app/controllers/visits_controller.rb
class VisitsController < ApplicationController
# methods for Visits
end
Note that these must to be located in separate files, so that Rails can find the correct source file by the name of the object that it's looking for. This is a Rails naming convention.
Regarding your routes, you'll need to change to use one of 4 route formats. Reading the section on Adding More RESTful Actions in the Rails Routing from the Outside In guide might help.
1) To route visits as a nested resource, which is what it appears you're actually trying to do, you would use this:
resources :subscribers, except: :show do
resources :visits
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/:id/visits/new
POST /subscribers/:id/visits
GET /subscribers/:id/visits
GET /subscribers/:id/visits/:id
GET /subscribers/:id/visits/:id/edit
PATCH /subscribers/:id/visits/:id/update
DELETE /subscribers/:id/visits/:id/destroy
This is the typical route structure for nested resources and separate controllers.
2) To make visits#new a simple collection (non-member) action in the VisitsController, then you likely want this:
resources :subscribers, except: :show do
collection do
get 'visits/new', to 'visits#new'
post 'visits', to 'visits#create'
end
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/visits/new
POST /subscribers/visits
This is typically used to add new top-level routes in an existing resource and controller.
3) To construct visits as member actions, use this:
resources :subscribers, except: :show do
member do
get 'visits/new', to 'visits#new'
post 'visits', to 'visits#create'
end
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/:id/visits/new
POST /subscribers/:id/visits
This is normally used to add new member routes in an existing resource and controller.
4) To simply make visits routes appear to be included in subscribers, you could use this:
get '/subscribers/visits/new', to: 'visits#new'
post '/subscribers/visits', to: 'visits#create'
resources :subscribers, except: :show
This will produce these routes:
GET /subscribers/visits/new
POST /subscribers/visits
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
This may be used to make arbitrary routes appear to be included in an existing resource, when they really may be independent.

Ignore part of the path in Rails 3

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

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 - How to change the parameter :id in resources

In routes.rb,
resources :projects
gives the following routes
/projects/
/projects/:id
While using nested resources like
resources :projects do
resources :photos
end
it gives the following routes
/projects/
/projects/:id
/projects/:project_id/photos
/projects/:project_id/photos/:id
This gives me the problem because I have to write controller specific before_filter choosing between params[:id] and params[:project_id] for doing Project.find(params[:project_id] || param[:id])
Is there any way to change the routes to have :project_id itself for all routes?
/projects/
**/projects/:project_id**
/projects/:project_id/photos
/projects/:project_id/photos/:id
Another way is define method, that find current project by :project_id, in ApplicationController
def current_project
#current_project ||= Project.find params[:project_id]
end
And redefine this method in ProjectController
def current_project
#current_project ||= Project.find params[:id]
end
Then, you can use current_project in filters for all your controllers
Name of resource's id param can't be changed
But you can redefine it as non-restful path before resourses
get "projects/:project_id" => "projects#show"
# etc. for all other 3 methods
I think what you're looking for is Shallow Nesting: http://edgeguides.rubyonrails.org/routing.html#nested-resources
Look down to 2.7.2 Shallow Nesting

Resources