How to handle role-based views in MVC? - model-view-controller

Regardless of the language or MVC framework used, how should I handle different views based on roles?
For example (pseudo code):
views/post/show:
<% show post here %>
if (role.isAdmin or role.isModerator) {
<% show moderation tools %>
}
<% show rest of content %>
I don't quite like the idea of putting too much business logic into the view, but it doesn't seem like there're other options. Are there?
This gets messier and messier once you have more roles, or different levels of permissions. Take this site for example. Users with more than 200 rep see less ads. Users with more than 500 rep have a retag button. Then you get an edit button at 2000, a close button at 3000, moderation tools at 10k, and more functions if you are a "star" moderator.

You can make this a little neater by having a custom ViewModel with a boolean property called ShowModerationTools. In your controller you perform the checks to see whether the current user, based on their roles, can see the post and set ShowModerationTools to either true or false. You then return the custom ViewModel to the view. That way you can then just do this in your view:
<% show post here %>
if (Model.ShowModerationTools) {
<% show moderation tools %>
}
<% show rest of content %>
It also means if your business rules change (for instance you need to introduce another condition) you just change the controller and don't need to alter your view.

There is nothing wrong with this. It's not business logic. It's presentation logic.
Question that this if answers is: should we show moderation tools?
It's not like: 'should regular user be able to delete whole history of payments' or something.

I agree with Arnis L. - but will add the following.
You could do this instead...
<% show post here %>
<% Html.RenderAction<MyControllerName>(m => m.ModerationTools()); %>
<% show rest of content %>
This has several benefits.
It encapsulates the Model and View required for moderation tools in a single Action.
It will probably remove the need for you to even have the role on the Model in the page you've posted as an example
It can be re-used on other pages.

Personally, I think what's more important here is the difference between good and good enough...
While technically I think that classifies as business logic, for such a simple case I don't see a problem including it in the view.
Now, if you had more complex logic, I'd suggest creating a new view for each "logic branch", and choosing between them in the controller.

Related

Rails: Use render & partial helper. Random order display?

I tried to use refactor my show.html.erb using:
<%= render #question.answers %>
into
_answer.html.erb
My understanding is that the partial here will be rendered once for EACH answer that is in the #question.answers collection.
As the render method iterates over the #question.answers collection, it assigns each answer to a local variable named the same as the partial, i.e. answer
However, I realized that every time when I refresh the page, the order of the content inside answer will be random. It is not listed in descending/ascending order, just like #question.answers.each |answer| does.
Any way to fix?
I kinda of solve it temporarily by putting
default_scope {
order("created_at ASC")
}
into my model.
Now when I refresh the page, the order of answer content will be the same order.

Rails form, load new record page rather than redirecting to it

I'm creating my first app in rails.
Basically, I have a new customer form, normally when you enter a new customer you are redirected to the record you created.
However as I am loading all my pages via ajax I want to load the new record in rather than re-direct to it.
I already have the form firing via ajax, I just need to know how I can access the new record URL to load it into my container.
Hope that makes sense. Thanks in advance!
You can add an option :remote => true to your form_for helper method, so that instead of page redirect the form gets posted via ajax.
For Ex :
<%= form_for(#post, :remote => true) do |f| %>
...
<% end %>
Then create a new template named create.js.erb which will get rendered after create method has been executed.
In this template, you can display the details of the new record you created.
For Ex :
$('some_element').replaceWith('<%=#posts.name %>');
Edit: You mentioned using load in your javascript. I would generally avoid this as you're making two round trips to the server. 1) Create 2) Get html to load into a div. Additionally, if there is an error that happens, it will be more difficult to catch and do the "good thing", whatever that might be.
Once you've added the remote: true option to your form, you need to deal with what happens on the server side of things.
Assuming your using REST, you'll have a controller with a create action in it. Normally, this method is creating the item and then subsequently returning the HTML for the create page. Instead of this, we need to create a javascript view for the action. This will tell the calling page what to when this action is hit.
Let's assume your form has a div called "new_record_form" and that you have another div called "new_records". You'll want to blank out the form elements in the form, effectively resetting it. You'll also want to add the new record to the "new_records" div.
To add the record to the new records div, you might do something like this.
$("#new_records").append("#{#new_record.name}");
When you submit the form, you should see this added. One great way to debug issues is to use the inspector. If you're in chrome, right click anywhere, inspect element and select network. Do this prior to form submission. You'll be able to see the ajax call and the response. Your logs will also be helpful.
Don't forget to blank out the form as well.
Note: You mentioned all your pages are ajax, but I highly suggest you evaluate if this makes 100% sense due to the various issues that result. Not saying this is the best article on the subject but might be worth a read.

.Net MVC3 Getting a Reference to the Page from a Controller

What I'd like to do seems pretty simple. I'd like to specify the page title in my #Page directive on a view, like so:
<%# Page Title="About This Site" ... %>
Then, I'd like to get that page title into the ViewData, so that it could be used in the master page, like:
<head>
<title>The Awesome Site | <%=ViewData["Title"]%></title>
And also in the View itself like:
<h1><%=ViewData["Title"]%></h1>
I am already inheriting a custom controller for all of the page controller methods. So I'm hoping that there's something I can do in the controller to transfer the Page.Title property into the ViewData (or maybe I'll use the ViewBag). I just can't seem to find any route to reference the Page from within the Controller.
Is this possible? Is there a different approach I might want to consider.
See "generic errors" post on this for a clean way to do this.
Passing data to Master Page in ASP.NET MVC
you should not be referencing anything from a "page" from a controller. The two are completely separate by design. You need to package your data and then send it to the view.
Another way (more like what you are requesting) is that you use
#{
ViewBag.Title ="my view title";
}
Then the master page uses that value wherever you want it.
I think you might be looking for Html.Title().
You can write this to any part of the page:
<title><%: Html.Title() %></title>
This will pickup the title from your view and insert it into your master page as required.

Best MVC design for rendering a grails template and passing data to this template?

I have a template that renders a chart according to a given list (retrieved from my domain layer). This template is displayed several times (with a different list) on a same page (let's say "home page") and in several pages as well.
For instance,
<g:render template="/template/chart" model="order: 'asc', orderBy: 'age', max: 5, domainClass: Person"></g:render>
should show a list of 5 persons ordered by age in ascendant order.
But the problem is that I need to construct the list of persons by querying the domain layer and unfortunately this can be done only through the template code like :
<%
def vizuService = grailsApplication.classLoader.loadClass('com.myapp.VizuService').newInstance()
%>
<img src="${vizuService.getChartImage(order, orderBy, max, domainClass)}"/>
//The service method getChartImage will query the DB
This design is flawed because I am retrieving my data through my View layer. But,
I cannot reasonably send the data list to the template through the home page controller because the controller might even NOT know how many charts (and what data) are displayed in the Home page.
So, How can I design it in a MVC manner? (i.e. the Controller should be responsible for getting/passing the data). Is TagLib the only way to do it? (and IMO, TagLib does not really respect MVC principles) Maybe, AJAX is the (only) solution?
Thx for your ideas.
I have solved my problem by using the include tag that allows to include partial views rendered by the response of another controller/action

Average Price - Appropriate logic to perform in the View or better in the Controller?

Would it be ok to put an "average price" calculation, as below, in the view?
Or is this against MVC and is it better to do this in the controller?
<p>Average price: <%= #seller.total_sales / #seller.num_sales %></p>
Neither. Put it in the model. Then it becomes easy to unit test.
Ask you several things:
Will this average price will be often displayed
Is it a part of a view (is it used to display something ?)
Does it need complex things to get/calcul/retrieve or whatever ?
If you think it's just an hint for your user, it's used only once, then you can let it in your view.
But if you feel unconfortable with it, or you need to do more complex maths on the price, put it your model..
Put your business logic where it belongs in the model:
<p>Average price: <%= #seller.get_average_price () %></p>

Resources