Namespacing Controllers & nesting Resources - ruby-on-rails

Is it a terrible idea to use both Name-spacing and nested resources?
I want to have an admin area, with a bunch of controllers in them. Some resources in that area would make sense to be nested for example:
resources :suppliers do
resources :products
resources :locations
end
Whilst namespacing like this:
map.namespace :admin do |admin|
resources :suppliers do
resources :products
resources :locations
end
end
Is it possible / a good idea to use nesting within a namespace like this? How should i structure things?

Namespacing an admin area is a good idea as it keeps those controllers separated from your public/user facing controllers. The big win here is security since your admin actions are likely to be capable of doing more and may bypass certain security restrictions like removing or limiting the amount of authorization, depending on how you want to structure your administration access.
As for nesting resources, use it if it makes sense. If you never want to access one of the nested resources outside of the context of it's parent resource, then using nested resources is a good option.
As an example, if your admin interface was to be accessed by suppliers, and each admin was to be scoped to their resources only, then it might make authorization simpler to nest the resources since you can simply query through that nested resource and your authorization is simplified to checking that their account is tied to that supplier.
class Admin::ProductsController < AdminController
before_filter :load_supplier
# your actions
def load_supplier
# Will trigger a 404 if the supplier does not belong to the admin
#supplier = current_admin.suppliers.find(params[:supplier_id])
end
end
Of course it really depends on what your trying to accomplish, what is the expected audience of the admin area, will they have full access to everything. If so will they need access to resources outside of the context of any relationships. For example what if I'm an admin and I need to do some search/sort/filter on all of the products, regardless of the supplier (or maybe filtering by one or more suppliers), and then generate CSV/Excel from those constraints. In this case using nested resources might make this difficult or impossible.
I've personally found that nested resources make more sense in user/public facing controllers and to be more annoying in admin areas, but then I've always built admin interfaces that were limited to few people. In which case I typically turn to a gem like ActiveAdmin which basically gives you a full CRUD on each model with plenty of customization options.

Related

correctly configuring nested resources with shallowing

I've ran into my first issue with nested resources, and from the documentation
http://guides.rubyonrails.org/routing.html#limits-to-nesting
I'm not entirely able to figure out how to make sense of this, and how to apply it correctly to my situation, currently my things are setup like this:
resources :stores do
resources :locations do
resources :business_hours
end
end
now I'd like to limit the nesting, the way they recommend but I'm uncertain on how to achieve this, as locations belongs to stores, and business hours belongs to locations.
What the rails documentation is essentially saying is that with your resource configuration above you'll have a url that will look something like this on your web page.
mywebapplication.com/stores/1/locations/1/business_hours/1
with the corresponding rails helper method for your code
stores_locations_business_hours_url
Now there isn't really anything wrong with that and you can do it this way but you'll start to run into tedious problems especially with your business_hours controller. The reason being is because for your controllers you will have to pass in every #model object preceding the following. You'll have to do something like
stores_locations_business_hours_url([#store,#location,#business_hour])
to access a page. To limit that you will need to do something like this:
resources :stores do
resources :locations, shallow: true
end
resources :locations do
resources :business_hours
end
So now instead of mywebapplication.com/stores/1/locations/1 the url will like this mywebapplication.com/locations/1 and now your business hours url will be one level deep. That's what is meant by the documentation.
To go with what Rails wants you doing, all you have to do is add shallow: true to each of your nested resources:
resources :stores do
resources :locations, shallow: true do
resources :business_hours, shallow: true
end
end
This produces, as they put it, "routes with the minimal amount of information to uniquely identify the resource", which look like this:
Prefix Verb URI Pattern Controller#Action
location_business_hours GET /locations/:location_id/business_hours(.:format) business_hours#index
business_hour GET /business_hours/:id(.:format) business_hours#show
store_locations GET /stores/:store_id/locations(.:format) locations#index
location GET /locations/:id(.:format) locations#show
stores GET /stores(.:format) stores#index
store GET /stores/:id(.:format) stores#show
The collection actions for locations, eg. index, get nested under stores because locations belong to stores, but to identify a specific location, the route references locations/1 without the stores/ prefix, because you don't need a store ID to identify the location.
This cascades down the tree: to identify the business_hours collection actions, you need the location the hours belong to, but because you have a location ID, you don't need the store involved, so you get locations/:id/business_hours. When you want a specific set of hours, you don't need the location anymore, so you just get /business_hours/1.
If you want to maintain the entire hierarchy for the hours collection paths (that is, /stores/1/location/2/business_hours), you need to either not shallow your locations paths, which will keep their member actions (show, edit, etc.) under /stores/1/locations/2, or you'll need to manually specify the paths you want using less of Rails' helpers.

Rails 4 custom admin backend

I'm wanting to create an admin backend for a practice app. After reading around I've come to a general idea of what to do but would like some clarification:
Use namespace to route the backend at example.com/admin...
Put any adminifiable resources inside namespace e.g resources :posts
At this point do I duplicate the normal (public facing) controllers and put them into the admin directory? (Along with CRUD views)
In theory the public facing controllers only need index and show actions, right? As new/create/update/destroy will only be accessed in the admin/controller.
Any clarification or advice is greatly appreciated. Just trying to wrap my head around this.
I would recommend against duplicating your controllers, or any part of your application for that matter. That goes entirely against the DRY principle, which stands for "Don't Repeat Yourself." Duplicate code becomes really hard to maintain and test as your application grows.
Instead, I would recommend limiting access to certain actions using before filters. For example, let's say that you want users to be able to create posts, read posts and see listings of posts. In your PostsController, you can have something like this:
before_action :admin_user?, only: [:edit, :destroy]
Note: before_action is just the new name for before_filters.
So then actions like index would execute normally for all users, but if a user calls the destroy action, the controller would first check to see if the user is an admin, by calling an admin_user? method (typically defined in ApplicationController). This method could be a simple conditional, like "if the user is not an admin, flash an error message and redirect them back to where they were before the request" and then use it to protect any action or resource you want. You could also use it in the views to show delete buttons on posts only if the user is an admin, for instance.
That's for resource-specific actions. Often times it's also a good idea to have a section of the site that consolidates resource views and administrative actions. This would be its own controller/view (I call mine AdminController) and you can protect all actions in it with the above method:
before_action :admin_user?
To make your resources available to AdminController using the methods defined inside the individual resource controllers, you can do this in routes.rb:
namespace :admin do
resources :users
end
This will make it so that http://yoursite.com/admin/users/index will still call the index action in the Users controller, but it will happen within the context of an admin user (because of the before_action above).

Best practice for further actions in rails controllers

i'm just writing my first app in rails and i wonder, if there is a best practice to do the following:
i have a customer model created by a scaffold and pumping it up. a customer has to be displayed in a google map, so if go to /customers/23, the customer information are displayed. additionally i have a link within this page to show the user in a map (with a query ui dialog that comes up via ajax).
The question for me is, how does this fits in the normal crud structure of the model. Should i do like creating an action, called "show_map" and give it an extra route additionally to the resources routes? How do you handle this kind of things?
Lets do it like
resources :customers do
resource :map, :only => [:index]
end
it will generate routes like this
{:action=>"show", :controller=>"maps"} customer_map GET /customers/:customer_id/map(.:format)

Rails Routes/Controller/Directory Structure design question

I have a design question where I would appreciate a thoughtful response.
Let's say you have a simple application (for example's sake) that has a User, Company and Theme model. A Company has_one Theme and has_many Users.
Administrators (a User) can fully manage Companies, Users and Themes - the whole REST stack, in addition to a few other actions too. Administrators are expected to do things to all 3 of these resources that other user roles cannot do.
We also have a Company role. This role can edit their own Company, as well as select a Theme from the ones the admin-user added as nice defaults, or they can just make their own theme.
Companies can also add/edite/delete users, but only for their company. These pages will have different views and they'll have different behaviour from admins - some overlaps, but some things will be restricted while others will be added.
Now, here we have some non-trivial design choices, and I would like to know what the best-practice is.
PART 1
In Rails, it makes sense to have resources :users, :companies, :themes for the administrators and probably resource :company, :theme, :users for the Company users.
But of course, we run into some naming conflicts here - both singular and plural - so we might want to try something like resource :my_company, :my_theme, :my_users to separate them? Or is there a better solution?
Furthermore, a theme is just a component of a company, so maybe we want to nest them?
:resource :my_company do
:resource :theme
:resources :users
end
This works okay, but it could be confusing as to which UsersController we are referring to... no? This is really sticky and I would love to know how to deal with this. Do you have 1 controller, or 2? What do you name them?
So this would be an example:
http://myapp.com/my_company/theme/edit
http://myapp.com/my_company/users/1/delete
Company users also might want the list of themes via ajax, so is it correct for them to call:
http://myapp.com/themes.json
?
Is this how to approach this situation, or is there a better way?
PART 2
Also, what should your directory structure look? Should you have controllers separated by user role?
/app/controllers/admin/companies_controller.rb
/app/controllers/admin/themes_controller.rb
/app/controllers/admin/users_controller.rb
/app/controllers/company/my_company_controller.rb
/app/controllers/company/theme_controller.rb
/app/controllers/company/users_controller.rb
Or is there better ways to handle this?
It seems weird that users_controller is duplicated 2x and that there is a minor difference between Theme and Themes.
I would really appreciate a thoughtful response on this. Thanks!
I appreciate your desire to organize your codebase as I constantly have to convince myself not to take my default impulse to nest a resource or namespace a model. As there is no right answer to this question, I will just offer the reasons I use to convince myself not to.
A resource lives in once place. User.find(1) should have a single locator (URL), which I like to call user_path. I like calling it user_path because of all the times that I have made myself call it admin_company_user_path([#company, #user]) which malaise makes in me each time I write it.
That resource may render itself in different ways for different situations, like if the requester was an XHR or indicated that they would prefer German to English. Why is the header indicating that the user is an administrator any different?
If I can make it look like the simplest examples in the rails/README, shouldn't I?
At this point I would concede and end up with:
/app/controllers/companies_controller.rb
/app/controllers/users_controller.rb
/app/controllers/themes_controller.rb
And my routes.rb would have:
resources :users
resources :companies
resources :themes
I should also address how I would handle the thing that makes you want to separate them in the first place–a different view for each of the roles. In my ideal scenario, my decision would result in a themes/_form.haml that looks like:
= form.input :title if user_can_change_title?
= form.input :theme if user_can_change_theme?
And the rest of the differences would handled in CSS, with perhaps a stylesheet for each role.
In the less ideal scenario, I might be using:
= render :partial => "#{current_user.role}_form"

How to organize controller in moderately large Rails application?

I'm working on an application with quite a few related models and would like to hear some opinions on how best to organize the controllers.
Here are some options I have been considering:
1) Namespace the controllers. So, for example, have a controllers/admin directory and a controllers/public directory. This seems appealing for organization but also sorta contrived since a single resource could often have actions which might sensibly belong in different directories (e.g. the show action is public whereas the create action is admin). So this would mean breaking up some of my resources into two separate controllers - one public, one admin. Seems bad.
2) Create nested resources. I've only occasionally used nested resources so it's not always clear to me when it's best to nest resources versus simply passing the data you need through the params explicitly. Does anyone has some suggestions/examples of how best to use nested resources? When is it a good idea? When is it an overkill?
3) Just leave the default scaffolded controllers alone. Create new collection/member actions where needed and use before filters to set permissions within each controller. This seems most appealing since it keeps things simple upfront. But I'm sorta nervous about things getting messy down the line as some of the controllers might start to bloat with several new actions.
If anyone with experience designing large applications could offer some guidance here, it'd greatly appreciated.
For organizing within our applications, I have done a little bit of everything depending on the situation.
First, regarding the separate controllers for admin/user functions, I will say that you probably don't want to go that route. We used authorization and before_filter to manage rights within the application. We rolled our own, but 20/20 hind-sight, we should have use CanCan. From there you can setup something like this (this is pseudo-code, actual language would depend on how you implemented authorization):
before_filter :can_edit_user, :only => [:new, :create, :edit, :update] #or :except => [:index, :show]
protected
def can_edit_user
redirect_to never_never_land_path unless current_user.has_rights?('edit_user')
end
Or at a higher level
before_filter :require_admin, :only [:new, :create]
and in your application controller
def require_admin
redirect_to never_never_land_path unless current_user.administrator?
end
It would depend which route, but I would use that for authorization instead of splitting up controllers.
As far as name spaces vs. Nested Resources, it depends on the situation. In several of our apps, we have both. We use name spaces when there is a cause for a logical separation or there will be shared functions between a group of controllers. Case and point for us is we put administrative functions within a namespace, and within we have users, roles and other proprietary admin functions.
map.namespace :admin do |admin|
admin.resources :users
admin.resources :roles
end
and then within those controllers we have a base controller, that stores our shared functions.
class Admin::Base < ApplicationController
before_filter :require_admin
end
class Admin::UsersController < Admin::Base
def new
....
end
This provides us logical separation of data and the ability to dry up our code a bit by sharing things like the before_filter.
We use nested controllers if there is going to be a section of code where you want some things to persist between controllers. The case from our application is our customers. We search for and load a customer and then within that customer, they have orders, tickets, locations. Within that area we have the customer loaded while we look at the different tabs.
map.resources :customers do |customer|
customer.resources :tickets
customer.resources :orders
customer.resources :locations
end
and that gives us urls:
customers/:id
customers/:customer_id/orders/:id
customers/:customer_id/tickets/:id
Other advantages we have experienced from this is ease of setting up menu systems and tabs. These structures lend themselves well to an organized site.
I hope this helps!
Also, looks like nesting resources more than one level deep is almost certainly a bad idea:
http://weblog.jamisbuck.org/2007/2/5/nesting-resources
Yeah, it's a old article, but it makes a lot of sense to me.
If anyone disagrees, I'd like to hear why.

Resources