acreadinglist
2010-Jan-24 00:31 UTC
Help for beginners? Nested resources, Authentication, and Authorization
In this thread, I hope to gather explanations from more experienced Rails users than I on how to approach the topics of nested resources, authentication, and authorization, areas that in my own experience as a Rails newcomer and based on my findings in newsgroups, forums, and articles, quickly present themselves as new users move beyond the tutorials to their first real apps. In the tutorials, the user first learns how to create a model and its corresponding views and controllers. The user then learns how to make another model, and associate it with the first. Let me start attaching some names. We''ll have two models: Business and Customer. And we''ll say there''s a many-many association (the details of which don''t matter here and aren''t the goal of this post). Businesses have many customers; customers have many businesses. I''ve chosen these two because, though there is a nesting relationship involved, the two are also distinct. In the guides.rubyonrails.org tutorials, the nesting relationship between Posts and Comments is different: whereas businesses and customers could very well stand alone in a commerce site, it seems much less likely that comments would be treated as a first class entity, since each comment is attached to a post. (I''m not trying argue about the nature of comments here, just trying to pick better models to exemplify the rest of the post). A new user following the tutorials as they build their app would likely first build their Business MVC. They might then build their Customer MVC, with or without addressing the association. Without the association, the user will be able to go about creating businesses and customers, with the usual CRUD and RESTful routing. Adding the association brings in a new change: following the tutorial''s example, nesting customers under businesses should leverage the CustomersController. So, calls to /businesses/:business_id/customers/ should route to the CustomerController. The index method for the CustomerController changes in kind. @customers = Customer.all is replaced by something along the lines of @business = Business.find (:business_id); @customers = @business.customers. With these changes, the user can no longer list all of the customers; he/she can only list all of a given business''s customers. We are now presented with the first question: 1) What is a/the good/best/recommended way to index a nestable but independent resource? In other words, what should be done to allow both the independent and nested views? Some possible solutions (for the case presented): - Have conditional behavior reliant on whether the :business_id parameter is present? (If so, get the nested customers, if not, get all) - Have different controllers for nested access and unnested access? - Have different routes and methods in the same controller for nested and unnested access? - Some other method? And at this point, the user encounters a new set of issues. The application will have users: businesses will access the app to see their customers, and court new ones; customers will want to view their favorite businesses, and look for others. [Let''s assume for the sake of this post that we''re not making a social app where everyone can see everyone else''s everything] - but businesses shouldn''t be able to see other businesses'' customers, and customers shouldn''t able to see other customers'' chosen businesses. If the user treads this path quickly, (and so that I can skip ahead a few steps), they''ll add an authentication plug-in and set up a bunch of callbacks, only to find themselves needing an administrator role: to 1) view all records as before, without any nesting, and to 2) act as if they were a given business or customer. For the benefit of any other beginners that have made it this far, there are two separate pieces at play here: Authentication and Authorization. Authentication handles identifying the user: which business or which customer has logged in? Authorization handles access: what pages is the user allowed to see? Around this point, my foundations falter, but I''ll try to proceed so as to reach what I believe are the next questions. As part of setting up their authentication mechanism, the user most likely will have added something like a :require_user callback (and may have put it in the ApplicationController) as a :before_filter. This callback in general sets @current_user; @current_user is then used throughout the controller. -- I''d like to interject here with a particular question of my own that is well reflected by the model choices here. Since ''users'' can refer to both businesses and customers here, how should authentication be handled? Should there be one user login/session that associates with a business or customer, and sets the appropriate @current_business or @current_customer in the require_user callback? Or, should there be separate business login/sessions and customer login/sessions, with their own callbacks? -- Some new questions arise from the presence of @current_user. Previously, the controller looked at the path parameters to determine, for example, which business'' customers to index. 2) How does routing and controller behavior change as a result of @current_foo ? - Should the RESTful, nested routing be maintained? e.g, / businesses/:business_id/customers - If so, how should :business_id be handled? --- Should it be ignored, so that regardless of :business_id, we always use @current_business ? --- Should it redirect to the URL corresponding to @current_business? - If not, should /businesses/:business_id/ be dropped entirely? --- e.g, when logged in as a business, /customers presents @current_business.customers , as managed by the CustomersController --- the /businesses/:business_id becomes implicit - and how should the logic be handled by the controller? - Some other change? 3) How should the relationship between the :id from the path param and the :id from the session be enforced? - Should there be additional callbacks to @require_user, say @require_specified_user to compare the session and path param :ids? - How should a mismatch be handled? --- Should an unauthorized message be displayed? --- Should the page be redirected to the closest relevant authorized page?/ - Some other method? And that leads to what is, at least for me thus far, the last question. Having reached this point, the user will have created a Business model and a Customer model, and been able to view all of the associated records. Then the user will have made associations, allowing the indexing of all of a given business'' customers, and all of a customer''s business. Hopefully, by adddressing question 1, the user will still have a means of indexing all customers and all businesses. The user will have also added one or more callbacks (a monolithic :require_user ?, :require_business and :require_customer that include the behavior of :require_user ?, :require_user that runs before :require_business or :require_customer ?), and a mechanism for enforcing the relationship between the session identification and the path parameters (from question 3). At this point, the user, who is now the Administrator, and neither a business nor a customer, wants to be able to view all the records, and also to be able to view the nested resources as seen by the ''users.'' 4) How should authorization for the administrator be handled? - If the answer to question 1 was to create a separate controller, the answer is simple: a :require_admin callback for that controller. - If the answer to question 2 was to maintain RESTful nested routing, should the :require_user (or equivalent callback) have an :unless => :as_admin? --- Thus falling back to the :business_id path parameter to identify the top level resource. --- How would this be handled if the controller was changed to always use, e.g, @current_business, in the solution to question 2? - Some method that works with the implicit logic from dropping the nested routing as in question 2? Hopefully this post captures some of the most common questions that Rails newcomers have as they''re delving into their first real projects. With Rails'' emphasis on on DRY, and proper separation of controller, view, and model logic, I''m really interested to see what, if any, are the common, clean solutions. If there are other common questions that I''ve missed, please add them. To anyone who responds, thank you in advance. I''m going to append one more question that is less of a core issue, but has just caught my attention. Thanks, Andrew APPENDIX Having set the nested associations as described above, there are now two ways to ''show'' a customer: /customers/:id and / businesses/:business_id/customers/:customer_id . The administrator wants the businesses to see only a limited set of the customer''s profile in the nested view; the customer, of course, can see all of its own data. A) What is a/the good/best/recommended solution to limit the business'' view? - Should there be two separate methods/views (e.g, CustomersController ''show'' and ''show_business'')? - Should the ''show'' view have conditional logic that hides data based on the presence of @current_business? - Should the ''show'' method render a different view for each case? -- You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en.