Rails routing with namespaces and nested routes - ruby-on-rails

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

Related

Rails => routing index to two different pages depending on the current_user

I'm using Rails and I have two views in bookings (clients and trainers). I'm trying to route from index to client or trainers depending on who is the current_user.
If I'm a client, route index to trainers
If I'm a trainer, route index to clients
class BookingsController < ApplicationController
def index
end
def clients
#bookings = Booking.where(client: current_user)
#clients = current_user.bookings.map(&:client)
end
def trainers
#trainers = current_user.bookings.map(&:user)
end
end
Rails.application.routes.draw do
resources :bookings do
collection do
get "/clients", to: "bookings#clients", as: "clients"
get "/trainers", to: "bookings#trainers", as: "trainers"
end
resources :shared_training_plans, except: [:new]
end
end
You're doing nesting backwards. And the output of your routes should actually look more like:
GET /instructors/1/bookings # bookings belonging to instructor 1
GET /instructors/1/clients # clients that are associated with instructor 1
GET /students/3/bookings # bookings belonging to student 3
GET /students/3/instructors # instructors belonging to student 3
This describes RESTfully that the endpoint returns something belonging to the other resource. This should be handled by the index action - ideally of a controller designated for that purpose as each controller should only be responsible for one resource.
You could define these routes as:
resources :instructors do
resources :bookings, module: :instructors
end
resources :clients do
resources :bookings, module: :clients
end
module Instructors
class ClientsController < ApplicationController
# GET /instructors/1/clients
def index
#instructor = Instructor.find(params[:instructor_id])
#clients = #instructor.clients
end
end
end
module Clients
class InstructorsController < ApplicationController
# GET /clients/1/clients
def index
#client = Client.find(params[:client_id])
#clients = #client.instrucors
end
end
end
You can implement this by reimagining the feature so that you link the "specific users URL" instead of a generic URL. You can see this for example on the dashboard here on Stackoverflow which uses https://stackoverflow.com/users/{id}/{username}.
REST is in theory supposed to be stateless so each path should return the same resource no matter who is requesting it. But like all rules this could be broken and you can set up routes like:
GET /user/bookings # GET bookings for the currently signed in user
Which would return different results for the signed in user which is stateful. user here would be consider a singular resource. This is mostly useful if you need to have a significantly different represention of the resource when viewing "your own" vs "others".
To actually be able to use the "current user" from the routes the authentication system must be implemented as Rack middleware (like Devise is) and not on the controller level as almost all the "reinventing the wheel" tutorials are. Routing is Rails is implemented as Rack middeware that is run before your controller is ever instanciated.
Devise has the authenticated routing helper which lets you setup different routes for authenticated/unauthenticated users. For example if instructors are implemented as different warden scopes:
authenticated :instructor do
resource :bookings, controller: 'instructors/bookings', only: [:index]
end
authenticated :student do
resource :bookings, controller: 'students/bookings', only: [:index]
end
But you could also use a lambda if you only have one warden scope (the typical Devise configuration):
authenticate :user, ->{|u| u.instructor? } do
resource :bookings, controller: 'instructors/bookings', only: [:index]
end
authenticate :user, ->{|u| u.student? } do
resource :bookings, controller: 'students/bookings', only: [:index]
end
Both of these would route GET /bookings to Instructors::BookingsController#index and Students::BookingsController#indexdepending on which "role" the user has.

Rails Best API design to get all of a record associated with a certain user

This is mostly an api design question. I have a Rails api which has routes for Users and routes for Schools. I would like to make a single call from my front end application to the api with a param of UserId which returns all of the schools associated with that user.
What is the best way to do that? Should I create a new route in UsersController called user-schools? Or a new route in SchoolsController called schools-user? Or create an entirely new controller called user-schools? Thanks for any guidance!
PS: Getting the records from ActiveRecord in the controller is not the problem. The problem is how to best design this api.
The RESTful way to define this would be through a nested route:
GET /users/:user_id/schools
The same basic design principles apply here for API and "classic" applications.
You can define this by nesting the calls to the resources macro:
resources :users do
resources :schools, only: [:index]
end
This will route /users/:user_id/schools to SchoolsController#index. While you can "sniff" for the user_id param:
class SchoolsController
# GET /schools
# GET /users/1/schools
def index
schools = if params[:user_id].present?
user = User.find(params[:user_id])
user.schools
else
School.all
end
render json: schools
end
end
A cleaner design is to use a seperate controller for the nested context:
resources :users do
resources :schools, only: [:index], module: :users
end
module Users
class SchoolsController < ApplicationController
# GET /users/1/schools
def index
user = User.find(params[:user_id])
render json: user.schools
end
end
end
This controller only does a single job. You could also name it UserSchoolsController but splitting your controllers into folders (and namespaces) makes it easier to organize them.

Rails 4.1: Creating routes by calling method in controller

I want to create a method that, when called from a controller, will add a nested resource route with a given name that routes to a specific controller. For instance, this...
class Api::V1::FooController < ApplicationController
has_users_route
end
...should be equivalent to...
namespace :api do
namespace :v1 do
resources :foo do
resources :users, controller: 'api_security'
end
end
end
...which would allow them to browse to /api/v1/foo/:foo_id/users and would send requests to the ApiSecurityController. Or would it go to Api::V1::ApiSecurityController? It frankly doesn't matter since they're all in the same namespace. I want to do it this way because I want to avoid having dozens of lines of this:
resources :foo do
resources :users, controller: 'api_security'
end
resources :bar do
resources :users, controller: 'api_security'
end
Using a method is easier to setup and maintain.
I'm fine as far as knowing what to do once the request gets to the controller, but it's the automatic creation of routes that I'm a little unsure of. What's the best way of handling this? The closest I've been able to find is a lot of discussion about engines but that doesn't feel appropriate because this isn't separate functionality that I want to add to my app, it's just dynamic routes that add on to existing resources.
Advice is appreciated!
I ended up building on the blog post suggested by #juanpastas, http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4, and tailoring it to my needs. Calling a method from the controllers ended up being a bad way to handle it. I wrote about the whole thing in my blog at http://blog.subvertallmedia.com/2014/10/08/dynamically-adding-nested-resource-routes-in-rails/ but the TL;DR:
# First draft, "just-make-it-work" code
# app/controllers/concerns/user_authorization.rb
module UserAuthorization
extend ActiveSupport::Concern
module ClassMethods
def register_new_resource(controller_name)
AppName::Application.routes.draw do
puts "Adding #{controller_name}"
namespace :api do
namespace :v1 do
resources controller_name.to_sym do
resources :users, controller: 'user_security', param: :given_id
end
end
end
end
end
end
end
# application_controller.rb
include UserAuthorization
# in routes.rb
['resource1', 'resource2', 'resource3'].each { |resource| ApplicationController.register_new_resource(resource) }
# app/controllers/api/v1/user_security_controller.rb
class Api::V1::UserSecurityController < ApplicationController
before_action :authenticate_user!
before_action :target_id
def index
end
def show
end
private
attr_reader :root_resource
def target_id
# to get around `params[:mystery_resource_id_name]`
#target_id ||= get_target_id
end
def get_target_id
#root_resource = request.fullpath.split('/')[3].singularize
params["#{root_resource}_id".to_sym]
end
def target_model
#target_model ||= root_resource.capitalize.constantize
end
def given_id
params[:given_id]
end
end

Rails routing and controller modules -namespacing?

I have trouble creating a module for my controller, and getting my routes to point to that module within the controller.
Getting this error:
Routing Error
uninitialized constant Api::Fb
So, this is how my routes are set up:
namespace :api do
namespace :fb do
post :login
resources :my_lists do
resources :my_wishes
end
end
end
In my fb_controller i want to include modules that will give me paths like this:
/api/fb/my_lists
This is some of my fb_controller:
class Api::FbController < ApplicationController
skip_before_filter :authenticate_user!, :only => [:login]
include MyLists # <-- This is where i want to include the /my_lists
# namespace(currently not working, and gives me error
# mentioned above)
def login
#loads of logic
end
end
The MyLists.rb file(where i define a module) is in the same directory as the fb_controller.rb.
How can i get the namespacing to point to my module inside of the fb_controller, like /api/fb/my_lists ?
The namespace you have set up is looking for a controller class that looks like this
class Api::Fb::MyListsController
If you want to have a route that looks like /api/fb/my_lists but you want to still use the FbController instead of having a MyListsController you need to set up your routes to look like this
namespace :api do
scope "/fb" do
resources :my_lists, :controller => 'fb'
end
end
In my opinion, instead of including a module MyLists in your FbController seems kind of awkward.
What I would probably do is have a module FB with a generic FbController then have MyListsController < FbController. Anyway, this is beyond the scope of your question.
The above should answer for your needs.
EDIT
From your comments, and my assumptions on what you're trying to do this is a small example:
config/routes.rb
namespace :api do
scope "/fb" do
post "login" => "fb#login"
# some fb controller specific routes
resources :my_lists
end
end
api/fb/fb_controller.rb
class Api::FbController < ApiController
# some facebook specific logic like authorization and such.
def login
end
end
api/fb/my_lists_controller.rb
class Api::MyListsController < Api::FbController
def create
# Here the controller should gather the parameters and call the model's create
end
end
Now, if all you want to create a MyList Object then you could just do the logic directly to the model. If, on the other hand, you want to handle some more logic you'd want to put that logic in a Service Object that handles the creation of a MyList and its associated Wishes or your MyList model. I would probably go for the Service Object though. Do note, the service object should be a class and not a module.
In your example, Fb isn't a namespace, it's a controller. The namespace call is forcing your app to look for a Fb module that doesn't exist. Try setting up your routes like this:
namespace :api do
resource :fb do
post :login
resources :my_lists do
resources :my_wishes
end
end
end
You can optionally define a new base controller for the API namespace:
# app/controllers/api/base_controller.rb
class Api::BaseController < ApplicationController
end
If you do so, your other controllers can inherit from this:
# app/controllers/api/fb_controller.rb
class Api::FbController < Api::BaseController
end
Running rake routes should give you an idea of how your other controllers are laid out. Just a warning - it's generally not recommended to have resources nested more than 1 deep (you're going to end up with complex paths like edit_api_fb_my_list_my_wish_path). If you can architect this in a simpler way, you'll probably have an easier time of this.

How can I pass parameters to nested resources for single table inheritance in Ruby on Rails?

I am having some difficulties passing in parameters to my controller. I created an Single table inheritance model in my model file.
class Account < ActiveRecord::Base
belongs_to :user
end
class AdvertiserAccount < Account
end
class PublisherAccount < Account
end
I setted up my routes table with nested resources
resources :advertiser_accounts do
resources :campaigns
end
I want to be able to pass the current account_id (an account_id from one of my two subclasses of account) to my campaign controller file.
A URL that I would use is http://127.0.0.1:3000/advertiser_accounts/1/campaigns
Since my resource for the url is advertiser_accounts and not accounts, I am not able to get the parameter :account_id.
class CampaignsController < ApplicationController
def index
#account = current_user.accounts.find_by_id(params[:account_id])
end
end
is there a shortcut to get the current resource or the id? Am I passing in parameters correctly? It seems confusing to call many find_by_id in the controller. Any help is appreciated.
Edit Possible solution:
One of the solutions that I was thinking was setting a type in my routes and then in my controller I would use case statement then get params[:advertiser_account_id] but that seems very tedious and messy. Especially if I will need to copy and paste a list of case statements in each action.
routes.rb
resources :advertiser_accounts, :type => "AdvertiserAccounts" do
resources :campaigns
end
campaigns_controller.rb
def index
case params[:type]
when "AdvertiserAccounts"
#account = current_user.accounts.find_by_id(params[:advertiser_account_id])
when "PublisherAccounts"
#account = current_user.accounts.find_by_id(params[:publisher_account_id])
end
end
Try this out:
resources :advertiser_accounts, :as => "account" do
resources :campaigns
end
that should give you
/advertiser_accounts/:account_id/campaigns/:id(.:format)
You can try with "becomes" method in your controller.
In your private method where you're looking for the account_id you would have:
#account = Account.find(params[:account_id]).becomes Account

Resources