A fictitious Rails app has the following resources:
Photographers
Images
Comments
A Photographer has many Images, that have many Comments
Each photographer has a login and is able to view, upload, edit and delete their images, comments as well as their own profile.
An administration interface is available and can edit both images, photographers and comments.
Furthermore, the photographer, their images and their comments are available from a public interface without login where visitors can add comments.
My question is: What is the Rails-way of structuring the controllers? I was thinking of going with namespaces for each 'role' (public, account, admin) like this:
# For administrator
Admin::PhotographersController
Admin::ImagesController
Admin::CommentsController
# For a logged in photographer
AccountController (?)
Account::ImagesController
Account::CommentsController
# For public
PhotographersController
ImagesController
CommentsController
However - some of the methods of these controllers are overlapping. Is this the best way, even though it's not that DRY?
Thanks!
If they are overlapping, you could extend the base controllers into the account/admin namespaces. eg you do your ImagesController which is for the actions everyone can see. This extends ApplicationController as normal. Then you do your admin version of ImageController, and that extends ImagesController. Then you add/override methods in the admin version for the required different behaviours, or it may just be as simple as adding a couple of before filters such as require_admin for example, which checks that current_user is an admin user, and redirects them away if not.
as you said, this is not really DRY. at least, you could structure routes and controllers to act for all the requirements, for example:
resources :photos, :only => [:index, :show] # offer only index and show actions to public
scope "/admin" do
resources :photos # full access for logged in users
end
if you also need index and show actions, you can add some checks inside them to load a different view (say you have a public and admin layouts).
another way could be to have a unique layout, no /admin/ sections and offer editing features to logged in users. so if logged and owner of some photo, allow editing and show context links. it's a matter of tastes :P
Related
I'm pondering over what would be the best solution for the following scenario:
We have a basic website which everybody can navigate without sign up/sign in. The controllers and views live under app/controllers and app/views/model_name.
When the user signs up and logs in (with devise) she should be redirected to a different controller under app/users/ and correspondingly to different views under app/views/users/.
I know, I could render different partials based on signed_in? from my views and I could also redirect from the default controller to the controller nested under the user directory, but I feel there must a more general approach.
I'm wondering if it could be done with routing alone (and of course having the directory structure with the different controller/view pairs in place).
Example:
app/
controllers/
albums_controller.rb
users/
albums_controller.rb
views/
albums/
index.rb
users/
albums/
index.rb
Edit: I rewrote this post being a little bit more specific and got pointed towards the solution which consisted in overriding after_sign_in_path_for.
I can think of two ways:
Combine a redirection with a custom constraint
Create a sub-class of ApplicationController:
class SignedOutApplicationController < ApplicationController
before_action :redirect_if_signed_in
...
end
and make it the sub-class for all of the non-user controllers. For symmetry I would create a SignedInController
I am planning to launch a website that acts as a sort of courier service where clients ask my company to find a certain product(for example an action figure) from another country that is not available in their country.
Spreecommerce(including some extensions) offers almost all the functionality that I require.
Shopping Cart, Payment system, CMS, Open Authentication, Commenting system.
One important piece of functionality remains, that is an Enquiry system. with this system customers can request certain products they want.
The form they will use will have the following information:
Name Description
Price(optional)
Barcode(optional)
Category(Using the same taxon system as products)
Quantity
Images
Comments (using the spree-contrib/spree_comments)
My idea is that the user will be presented will have a form which when submitted the admin can view. The admin can view/modify the enquiry and respond using the commenting system. Once the item is located and confirmed, the admin would then create a product and add it to the user’s shopping cart which the user can finalise using the normal spree order check out system.
So my modifications to the system would be:
- Add the Enquiry form for both client and administrator(administrator can place enquiries in the name of the customer)
- Make Products private to the users who ordered them
- Disable the “products” page for users as each product is unique to each user
I am at the point of creating the Enquiry form page. I looked at the spreecommerse documentation and there doesn’t seem to be instructions on adding a new page and integrating it into the system. The documentation focuses more on modifying current pages.
Therefore I decided to view a project that implemented somewhat similar functionality and emulate what it did.
I went with spree-contrib/spree_static_content. So what I did was:
Create an enquiry migration using spree_products tables as guidelines with the following information:
enquiries table
enquiries_taxons table(Used for “categories”)
enquiries_variants table(Used for “image uploads”)
Created the following views under app/views/spree/admin (These views are mostly simplified versions of the product views) :
enquiries/_form.html.erb
enquiries/edit.html.erb
enquiries/index.html.erb
enquiries/new.html.erb
shared/_enquiries_sidebar_menu.html.erb
shared/_enquiry_tabs.html.erb
To add the menu item in the admin section:
app/overrides/add_enquiries_to_admin_main_menu.rb
Created an empty EnquiriesController that inherits from Admin::BaseController
Created an Enquiries model that inherits from Spree::Base and is loosely based on the Products model.
Things that I am confused about so far are:
How is CRUD handled?
Where are the spree.admin_{name}_url values being stored?
If there are any tutorials on how to properly create such functionality, it would also be greatly appreciated.
Hmm, why not use the Spree::Product model as a starting point? Just add custom attributes to that model/table. I suggest this because building your own tables and relations to variants and taxons seems like reinventing the wheel.
You've got a good start here. What you are missing so far is routes
spree.admin_{name}_url values being stored?
...you don't necessarily need to use the spree. namespace for routes if your controllers inherit from Spree::StoreController or Spree::AdminController. The Spree base controllers provide a bunch of useful CRUD logic. In your case you'd need a routes.rb file that looked something like this:
Rails.application.routes.draw do
mount Spree::Core::Engine, :at => '/'
end
Spree::Core::Engine.add_routes do
#public enquiries, e.g. that inherit Spree::StoreController
get 'enquiries', :to => 'enquiries#index', :as => :enquiries
end
Spree::Core::Engine.add_routes do
namespace :admin do
resources :enquiries
end
end
Hottip: check out the spree_scaffold gem. It makes stubbing out models/controllers/view/routes hella quick
(Using rails 4, Cancan 1.6.10)
Hello all,
I have two UsersController: one for the front, the other under /backoffice for the back, and both are using the same User model.
I defined abilities for a "normal user" like this :
can(:manage, User) do |u|
u == user
end
whereas an admin user will be able to manage all users:
can :manage, User
I also have two "show" views, one in the front: /users/:id and one in the backoffice: /backoffice/users/:id
My problem is that a limited user can today see its profile through the backoffice URL, as he "can Manage this user". (And of course this is not acceptable)
I know this should not be too difficult to correct, but what solution would you use?
There seems to be a way to namespace the abilities. See the Wiki entry of the continued project CanCanCan here: https://github.com/CanCanCommunity/cancancan/wiki/Authorization-for-Namespaced-Controllers
What it basically does is override the Ability class to pass a namespace that is determined by the ApplicationController. The Ability class has two different rulesets depending on the namespace it is initialized with.
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).
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.