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.
Related
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.
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
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
I have a nested resource for which I'm using Cancan to do authorization. I need to be able to access the parent object in order to be able to authorize the :index action of the child (since no child instance is passed for an :index action).
# memberships_controller.rb
class MembershipsController < ApplicationController
...
load_and_authorize_resource :org
load_and_authorize_resource :membership, through: :org
..
end
ability.rb
can [:read, :write], Membership do |membership|
membership.org.has_member? user
end
This doesn't work for the :index action
Unfortunately the index action doesn't have any membership instance associated with it and so you can't work your way back up to check permissions.
In order to check the permissions, I need to interrogate the parent object (the org) and ask it whether the current user is a member e.g.
# ability.rb
...
can :index, Membership, org: { self.has_member? user }
Cancan almost lets me do this...
Cancan states that you can access the parent's attributes using the following mechanism:
https://github.com/ryanb/cancan/wiki/Nested-Resources#wiki-accessing-parent-in-ability
# in Ability
can :manage, Task, :project => { :user_id => user.id }
However this just works by comparing attributes which doesn't work for my case.
How can I access the parent object itself though?
Is there any way to access the parent object itself within the permissions?
I recently faced the same problem and ended up with the following (assuming you have Org model):
class MembershipsController < ApplicationController
before_action :set_org, only: [:index, :new, :create] # if shallow nesting is enabled (see link at the bottom)
before_action :authorize_org, only: :index
load_and_authorize_resource except: :index
# GET orgs/1/memberships
def index
#memberships = #org.memberships
end
# ...
private
def set_org
#org = Org.find(params[:org_id])
end
def authorize_org
authorize! :access_memberships, #org
end
end
ability.rb:
can :access_memberships, Org do |org|
org.has_member? user
end
Useful links
https://github.com/ryanb/cancan/issues/301
http://guides.rubyonrails.org/routing.html#shallow-nesting
Can't you do something like this?
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :index, Membership, org: {id: user.memberships.map(&:org_id)}
end
end
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