Basically I've developed an app which has two namespaces: admin, api and the public one (simply resources :users for instance). All works fine, however I'm repeating myself quite a bit as some of the controllers in api for instance could easily be used in admin.
How can I DRY my code in this case keeping the namespaces?
Thanks!
There are a couple ways I can think of doing it:
(NOT RECOMMENDED) - Send the urls to the same controller in your routes.rb file.
Shared namespace that your controllers inherit from
For example you could have:
# controllers/shared/users_controller.rb
class Shared::UsersController < ApplicationController
def index
#users = User.all
end
end
# controllers/api/users_controller.rb
class Api::UsersController < Shared::UsersController
end
# controllers/admin/users_controller.rb
class Admin::UsersController < Shared::UsersController
end
The above would allow you to share your index action across the relevant controllers. Your routes file in this case would look like this:
# config/routes.rb
namespace :api do
resources :users
end
namespace :admin do
resources :users
end
That's definitely a lot of code to share one action, but the value multiplies as the number of shared actions does, and, most importantly, your code is located in one spot.
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.
What would be the preferred routing and namespace controller scheme for a scoped resource in Rails. ie. for index of projects that are scheduled:
1) /scheduled/projects
2) /projects/scheduled
3) /projects?scheduled=true
Option 1 is kind of what our existing codebase does, every new feature that access the resource differently get its own namespace. But it seems like option 2 is a possibility as well.
Looking for opinion from folks working on sufficiently large Rails codebase with a large number of controllers.
Option 1: scope is the namespace
scheduled/projects=> scheduled/projects#index
namespace :scheduled do
resources :projects
end
resources :projects
/projects/3
bin/rails g controller projects
class ProjectsController < ApplicationController
def show
render plain: "show projects/#{params[:id]}"
end
end
/scheduled/projects
bin/rails g controller scheduled/projects
class Scheduled::ProjectsController < ApplicationController
def index
render plain: 'index scheduled/projects'
end
end
Pros:
All the scheduled items are group under one folder, kind of similar to the the admin route scoping example in https://guides.rubyonrails.org/v5.2/routing.html.
Cons:
The various scoped ProjectsController are scattered in different places.
Option 2 - scope specified after the resource
projects/scheduled => projects/scheduled#index
namespace :projects do
resources :scheduled
end
# config/routes.rb
resources :projects
/projects/3
bin/rails g controller projects
class ProjectsController < ApplicationController
def show
render plain: "show projects/#{params[:id]}"
end
end
/projects/scheduled
bin/rails g controller projects/scheduled
class Projects::ScheduledController < ApplicationController
def index
render plain: 'index projects/scheduled'
end
end
Namespace scheme is similar to the Inboxes::PendingController example in.http://jeromedalbert.com/how-dhh-organizes-his-rails-controllers/
Inboxes is the resource like projects
Pending is the state like scheduled
Pros: Everything Projects related would be under the same namespace, could make it easier to share things like filtering logic
Cons: projects/scheduled index route could look similar to projects/:id show route with scheduled as the :id
Option 3 - scope is the query params
/projects?scheduled=true => projects/scheduled#index
Using advance constraint to map query params to routes.
Pros: all the projects related routes are simply /projects
Cons: maybe not all filters are applicable to a certain scoped resource. ie. /projects?scheduled=true&filter[status]=false may not make sense.
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 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.
In my "routes.rb" file I have the following line:
resource :users
which gives me a bunch of named routes for accessing my User model in a RESTful manner.
Now, I've made some additions to the User model including creating a special class of user. These are still stored in the User model but there is a "special" flag in the database that identifies them as special.
So, is it possible to create special_users resource? For example, I'd like to have a "special_users_path" as a named route to "/special_users" which will return an index of only the special users when you perform a GET on the URL.
Is there a way to do this?
In Rails routing, a 'resource' refers to the standard 7 routes that are created for RESTful resources: index, show, new, create, edit, update and destroy. Normally that is enough, but sometimes you might want to create another action.
In the model, you want to create a scope that only returns special users:
class User < ActiveRecord::Base
scope :special, where(:special => true)
end
On the controller side, there are two ways to go about this. What you are suggesting is the creation of an additional action:
match "/users/special" => "users#special"
resource :users
In the controller, your special action would return the scope you just created:
class UsersController < ApplicationController
def special
#users = User.special
end
end
That will do what you ask, but I would suggest NOT doing it this way. What if you add other flags later that you want to search by? What if you want to search by multiple flags? This solution isn't flexible enough for that. Instead, keep the routes the way they are:
resource :users
and just add an additional line to your controller:
class UsersController < ApplicationController
def index
#users = User.all
#users = #users.special if params[:special]
end
end
and now, when you want to display special users, simply direct the user to /users?special=true
This approach is much more future-proof, IMO.
(This answer is assuming Rails-3. If you're still using 2.3 let me know)
You could set the special_users as a resource:
resource :special_users
If you need to point it to a special controller, you could specify it with:
resource :special_users, :controller => :users
But I would really suggest you to not creating another controller for retrieving a kind of user, but using a param to get them:
class UsersController < ApplicationController
def index
users = case params[:type].to_s
when "special"
User.special_users # Using named scopes
else
User.all
end
end
end
When you use the users_path to call the special users:
users_path(:type => :special)