Rails- redirect_to carrying over unwanted id - ruby-on-rails

I am trying to redirect from a show action to a custom collection action, but the id param is being carried over, causing the routing to fail. A minimal example:
routes.rb:
resources :first_models, only: [:show]
resources :second_models do
get 'custom_action', on: :collection
end
first_models_controller.rb
class FirstModelsController < ApplicationController
def show
redirect_to controller: 'SecondModelsController', action: 'custom_action'
end
end
second_models_controller.rb
class SecondModelsController < ApplicationController
def custom_action
# Do something
end
end
After setting that up, navigating to /first_models/2 results in an error:
No route matches {:action=>"custom_action", :controller=>"SecondModelsController", :id=>"2"}
I cannot figure out how to strip out the id param from the original request so that the routing matches.

The reason why this happens is that you call redirect_to with a Hash argument. Internally Rails uses url_for to build the final location, which in turn uses default_url_options which uses the ID of the current resource. From the API docs:
Missing routes keys may be filled in from the current request's parameters (e.g. :controller, :action, :id and any other parameters that are placed in the path).
See: http://api.rubyonrails.org/v5.1/classes/ActionDispatch/Routing/UrlFor.html
Solution: Use a named path helper.
Run bundle exec rake routes on the command line to get a list of all your routes and named path helpers. Pick the one that you need and use it as follows:
redirect_to my_named_path_helper_path

It is not the param the problem:
class FirstModelsController < ApplicationController
def show
redirect_to controller: 'second_models', action: 'custom_action'
end
end
You can type rails routes and see all your routes and how rails recognize them.
This should work. However you can be more explicit and use:
redirect_to custom_action_second_models_path

Related

getting id from inside controller rails

I'm new to rails so I don't know the correct terminology.
I'm trying to get an id of a community from my subscription controller but I'm getting the error undefined method 'community_id' for nil:NilClass
my SubscriptionController.rb:
class SubscriptionsController < ApplicationController
def create
#subscription = Subscription.new(subscription_params)
#subscription.account_id = current_account.id
#subscription.save
redirect_to community_path(#subcription.community_id) //error here
redirect_to community_path(params[:community_id]) // also error below
end
end
def subscription_params
params.require(:subscription).permit(:community_id)
end
error from 2nd line try: No route matches {:action=>"show", :controller=>"communities", :id=>nil}, missing required keys: [:id].
The subscription is saving into the db. What am I doing wrong?
You're making three common rookie misstakes here:
You're assuming that the community_id passed by the user is actually valid and corresponds with an existing record. Never trust user input.
You're assuming that creating the record will always succeed. It won't - and you should always code defensively to handle that condition. For example if the user double clicks the button the second request should fail as it would lead to a duplicate record.
You're redirecting twice. Each request can only have one response and what you would be doing is just overwriting the LOCATION header unless Rails prevented you from doing it with a double render error.
Instead I would do this as a nested route:
# config/routes.rb
resources :communities do
resources :subscriptions, only: [:create]
end
class Account < ApplicationRecord
has_many :subscriptions
# ...
end
class SubscriptionsController < ApplicationController
before_action :set_community, only: [:create]
# POST /communities/1/subscriptions
def create
# I'm assuming that you are ensuring the account is actually signed in your ApplicationController
# note that you don't need to use strong parameters here as you're not mass assigning any parameters
#subscription = current_account.subscriptions.new(
community: #community
)
# Ensure that the user is notified if creating the record fails.
if #subscription.save
redirect_to #community,
success: 'You are now subscribed.'
else
# #todo provide a more specific failure reason
redirect_to #community,
error: 'Could not create subscription. Please try again later.'
end
end
private
def set_community
# this will bail early and return a 404 response if an invalid id is passed.
#community = Community.find(params[:community_id])
end
end
# app/views/communities/show.html.erb
<%= button_to "Subscribe", [#community, :subscriptions], method: :post %>
This places the community_id parameter as part of the URL itself and is how you RESTfully model relations between resources. The relation is explicit instead of tucked away into the request body. If you're ever passing record ids as hidden inputs you're most likely doing it wrong.

Rails routing with namespaces and nested routes

I have an email_template model that has a nested resource moves to handle moving an email_template from one folder to another.
However, I want to namespace these actions in a :templates namespace because I have several other resources that are template items as well.
Since I'm namespacing, I don't want to see templates/email_templates/:id in the URL, I'd prefer to see templates/emails/:id.
In order to accomplish that I have the following:
# routes.rb
namespace :templates do
resources :emails do
scope module: :emails do
resources :moves, only: [:new, :create]
end
end
end
Everything works fine when I do CRUD actions on the emails, since they are just using the :id parameter. However, when I use the nested moves, the parent ID for the emails keeps coming across as :email_id and not :email_template_id. I'm sure this is the expected behavior from Rails, but I'm trying to figure out how the parent ID is determined. Does it come from the singular of the resource name in the routes, or is it being built from the model somehow?
I guess it's ok to use templates/emails/:email_id/moves/new, but in a perfect world I'd prefer templates/emails/:email_template_id/moves/new just so developers are clear that it's an email_template resource, not a email.
# app/controllers/templates/emails_controller.rb
module Templates
class EmailsController < ApplicationController
def show
#email_template = EmailTemplate.find(params[:id])
end
end
end
# app/controllers/templates/emails/moves_controller.rb
module Templates
module Emails
class MovesController < ApplicationController
def new
# Would prefer to reference via :email_template_id parameter
#email_template = EmailTemplate.find(params[:email_id])
end
def create
#email_template = EmailTemplate.find(params[:email_id])
# Not using strong_params here to demo code
if #email_template.update_attribute(:email_tempate_folder_id, params[:email_template][:email_template_folder_id])
redirect_to some_path
else
# errors...
end
end
end
end
end
You could customize the parameter as:
resources :emails, param: :email_template_id do
...
end

Rails form_for not working as expected

I am trying to create a small rails blog, and have run into an error. I think I've messed up the naming convention of something here... but I can't find anything specific enough to help me and give me the answer.
I have a route of
resources :blog
and a controller which has the following
class BlogController < ApplicationController
def index
#blogs = Blog.all
end
def show
#blog = Blog.find(params[:id])
end
def new
#blog = Blog.new
end
end
When I try to set up a form on the new.html.erb page, it links to routing which states
undefined method `blogs_path'
My route is blog, not blogs... where am I going wrong? I would like the route to be /blog.
Following the conventions, rename your controller file to blogs_controller.rb and the class inside of it to BlogsController. Then, in your routes.rb: resources :blogs. It should all work fine.
As already said, it should be :
resources :blogs
And I think the controller should use the plural too : BlogsController
To find what's wrong with urls you can use rake routes command
Values in first column Prefix are names of helpers you can use as prefix_url or prefix_path

How to route url - Ruby

I'm creating a new webpage were my users can have profile pages, and I can't seem to find the way to make a url like this:
webpage.com/profile/username
To go to my profile_controller action index and use the variable that comes on /username.
But I can't seem to find the way.
profile_controller.rb
class ProfileController < ApplicationController
def index
profile_info = Profile.find(params[:username])
end
end
And I've tried to work it around with the routes.rb but couldn't make it...
This route in your routes.rb should map get requests on /profiles/username to the index action of your profiles controller and pass the username value in params[:username]
get '/profiles/:username' => 'profiles#index'

Single resource and multiple resources

I have a model Whitelabel and a User has_many :whitelables
I have a custom method current_whitelabel (like authlogic or restful_auth for current_user)
I want my users to manage their whitelabels (ie: edit_whitelabels_path(id)).
But I don't want to send the whitelabel ID in params when it refers to the current_whitelabel.
So my idea is to create two resources: map.resources whitelabels and map.resource whitelabel.
But I don't like this so much. Is there any sexier way to accomplish it ?
Ok I finally solved my problem.
Each whitelabel has his own subdomain (thanks to subdomain_fu), so I just need a single resource whitelabel in my routes to do action on my current_whitelabel and if I want to do action on others whitelabels, I just have to switch the subdomain.
Thanks EmFi for trying to answer to my strange question.
In your controller action you can do this:
class WhitelabelsController < ActionController
def edit
#whitelabel = params[:id] ? Whitelabel.find(params[:id]) : current_whitelabel
redirect_to whitelabels_url unless #whitelabel
....
end
...
end
Now rails will treat /whitelabel/edit as /whitelabel/edit/#{current_whitelabel.id} without specifying the id.
If this happens for more than one action you can do it as a before filter. Just be sure to remove all #whitelabel = Whitelable.find(params[:id]) lines from the individual actions.
class WhitelabelsController < ActionController
before_filter :select_whitelabel, :except => [:index, :new]
def select_whitelabel
#whitelabel = params[:id] ? Whitelabel.find(params[:id]) : current_whitelabel
redirect_to whitelabels_url unless #whitelabel
end
...
end
Answering the more clearly stated question in the comment:
You can use a singular resource in tandem with the above code to have the effect you want.
config/routes.rb
map.resource :my_whitelabel, :controller => "whitelabels", :member => {:dashboard => :get}
Then in the whitelabels controller use the above code. This keeps things DRY by using the same controller for different paths with the same actions. The resource defines a dashboard action, so you'll have to add that to the controller too. But if you're using the before_filter version there should be no problem.

Resources