Can a Rails Template Determine Which Controller Called It? - ruby-on-rails-3

In my Rails app, I want to update the contents of a header element in my application wide layout and have that content depend on which controller is handling the request. For example, if UserController is handling the request, then the header element content could be "User Page", but if the PhotoController is handling the request then the header element content could be "Photo Page". The solutions I've came up with (using content_for or setting instance variables) all seem to require code duplication and I'm looking for a DRY implementation. Is there a Rails variable that I can use in my view that reflects the current controller?

You can use the params[:controller] variable to determine this, or if you want something longer then controller.controller_name.

Related

All requests goes against the same action in Piranha

I've started using Piranha CMS and really enjoining it, but now I came into a bit of trouble.
One of my pages is a contact form where I want to post an extended page model with contact information to the controller.
I've created a page template Contact and in the manager gui and I've set the setting View to Contact
The correct view is loaded but the problem is that all the requests goes against the index action method and not the contact action method in PageController.
What am I doing wrong?
The view is used to signal the controller what view should be used to render the page, not which action should be called. This can be used when several pages has the same type of data and logic, but should be rendered differently.
The field route is used to rewrite a page to a controller/action. The default route for a page is Page which means that requests to that page is rewritten to ~/page, i.e the PageController and its Index action. If you wanted a certain type to be rewritten to the contact action of the PageController you set the route to Page/Contact which will rewrite the request to ~/page/contact.
If you have a complex structure you should add custom controllers, for examples a ProductController. The route could then be set to Product rewriting the request to ~/product.
I hope this clarifies things!
Regards
HÃ¥kan

rails render form after error in other controller

I have two models in the following releationship: Client has_many products, and Product belong_to client. In the client show view I present a form to create new products, that automatically belong to the current client. The show method in the client controller
def show
#client = Client.find(params[:id])
#products = #client.products.paginate(page: params[:page])
#product = #client.products.new
#product.client_id = #client.id
end
and the show view renders a partial
<h1>New Product:</h1>
<%= render 'shared/product_form' %>
That works, products are correctly created.
When an validation error occurs I set a flash in the product create method and redirect_to the client show page. There I loose the data that has been filled in correctly. I tried save the #product instance variable, which has all the data (including the wrong fields) doing
render client_path(client)
from the product controller, but that produces an error
Missing template /clients/17
with the address being
http://localhost:3000/products
Am I calling this wrong? I know that render ususally renders action of the same controller. Can I somehow render Client::show from the product controller? Is there another way to save the data the user has typed in?
If a validation error occurs you should redirect back to the page that generated the validation error. Ie: if the user is at products/new when they submit the form, then your products#create action should end with render :new to present the products form again.
If your products#create action is receiving the form from clients#show, then you do want to render clients#show with validation errors. In that case, all the information that was completed in the form will be available at params[:product], just like it is coming in to products#create.
You might want to take a look at another answer I wrote recently to understand the flow between controllers.
Specifically, the misunderstanding in your case is as follows:
When you have a validation error the record will not save, so you cannot "go back to that data" because your app has not kept it anywhere. The only copy of the data that was submitted is in the request.
If you REDIRECT you are not forwarding a request, you are responding to the initial POST request (which includes all the form information as params[:product]) by making a new request to a different url. This is why you want to use RENDER.
However, if you try to render clients_path(client), what happens is Ruby will first evaluate clients_path(client) to the string clients/(client.id), or in the example you gave, clients/17.
Then render tries to call render 'clients/17', which you don't have a template for. It's looking for a file called clients/17.html.erb. That's why you get the error you're getting.
So, again, to sum up - your products#create action receives the information that was sent from the form as params[:products]. That information is not available outside of this controller action. So, if there is a validation error, instead of creating the product, this controller action should render the same page that the user came from originally (normally products/new) so that they can see the form they just had (with the information filled back into it if you're using a form builder) and also see the error that prevented saving.
I hope that makes sense, feel free to ask follow-up questions.
Yes, you were calling it wrong.
There are three problems:
render should render a template name, say client/new, shared/form etc. The argument could not be a path or variable. The variable is passed by controller to view, nothing to do with render.
You should not use render for a saving fail. Even if you use a template name as #1 mentioned, you'll end up with a wrong URL like products/create with the client page. That's not acceptable.
My suggestion is to always use redirect for saving fail.
Minor problem. In controller, if you've used #product = #client.products.new, the #product object will have every attributes empty but with a valid client id. So you don't need to assign client id again by #product.client_id = #client.id. But this doesn't hurt the result.
Andrew's great answer plus this:
Where to render comments controller in Rails on model validations failure?
makes the solution more clear.
Your specific example: Just watch out that your create action in the ProductsController has all instance variables it needs to render 'clients/show' from it.

Rails 3 DRY Conditional Layout for iframes

I was recently tasked with loading a portion of my Rails application within an iframe on another website. The relevant pages should be using a different layout file, but only if they're being rendered inside of the iframe. There was a solution proposed here Detect iframe request in a rails app that involved passing a query string parameter.
For example the requesting website could call my application through an iframe with the src of http://foo.com/bar?iframe=true. Then in our controller we could simply check:
def bar
render :template => "iframe" if params[:iframe]
end
This seems like a good solution, but sadly that only works for the initial request as the original query string is completely static. Assuming we have accessible links to other routes within the iframe is there any way of easily relaying the iframe=true request parameter to maintain the correct iframe layout without having to repeat code? Basically I would like to take the DRYest approach possible without breaking any existing functionality. I considered creating another link_to helper which included the logic to relay this parameter if it exists and replacing all of my link_to calls throughout my application; I was wondering if anybody had a better approach though.
I decided to tackle this problem using JavaScript and added the following to my haml layout file:
:javascript
for(i = 0; i< $('a').length; i++)
{
if($('a')[i].href.match(document.domain))
{
$('a')[i].href = $('a')[i].href + "?iframe=true";
}
}
This coupled with my server-side checks for the iframe param will ensure that the appropriate layout is loaded. I decided to only cater this functionality to users who enable JavaScript so it might not be the best solution. The only other problem with this approach lies in controller redirects and forms where I have to manually check for the iframe param and then forward it accordingly - not DRY at all, but I was at least able to put the logic into a controller method. If somebody knowns of a better solution please feel free to leave an answer.

What should I prefer to use widget or renderPartial in Yii's view?

I am confused when I should use a custom widget or renderPartial in my view files. Sometimes I use widget and sometimes I use renderPartial.
Widget
You use widget when your application logic is defined in a separate CLASS file and the logic is somehow separated and standalone.
Widget's are chosen when the functionality is repeatedly used elsewhere, on lot of pages.
renderPartial
You use renderPartial for VIEW files that you want to embed into something bigger, or when you want to print something without using the application layouts.
renderPartial is chosen when all the variables it need to access are already prepared in the current action.
Widget
You can use widget when your site has some common part like header and footer or sometime some kind filter which require on every page of site.
renderPartial
Take example of search form of yii crude which is called by using renderPartial because that serach form is changing according to requirement of pages.
Sorry for english.

Setting returnURL for CButtonColumn button

I'm looking at the controller for the default Delete button in the CButtonColumn class. It manages to return to the previous web-page after deleting a CGridView line and remain on the same page of the CGridView, as opposed to going to the first page. The lines responsible for this in the associated controller seem to be:
if (!isset($_GET['ajax']))
$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
I would like to create a new custom button that has this behavior (i.e. returning to the previous view without resetting the pagination to page 1), but simply including the above lines of code in the button's associated action does not do the trick. I think I need to send that 'returnUrl' parameter somehow, but I cannot figure out how :)
The 'returnUrl' code you are looking at uses a POST variable for the returnUrl. To use this, you will need to POST that somehow. On the View this code is called from I am assuming there is a <input name="returnUrl"> field in the form. You should make sure this field (populated with the correct URL value) is on all of the Views you are POSTing from in order to access that POST variable in your Controller action.
If you are POSTing to the deleteAction via AJAX, I think you can set the $_POST['returnUrl'] variable with the jQuery AJAX function.
Another way to go might be to use CWebUser's returnUrl SESSION variable instead of this POST variable. I have never done this, but it's built in to Yii so I assume it works OK.
I never really liked the hacky $_POST['returnUrl'] that Gii generates anyway.
ANOTHER thing you could do, possibly, is look at the $_SERVER['HTTP_REFERER'] variable, and use that for the return redirect in your deleteAction. I don't know if that will be set correctly though, with complications from the 302 redirect/rewrites that Yii does.
Good luck!
You can set the return url via the CHtml::link call. Here is an example using delete
CHtml::link(
'Delete',
'#',
array('submit'=>array('delete','id'=>$model->id),
'params'=>('returnUrl'=>'controller/action...'),
'confirm' => 'Are you sure?'
)
);
Pulled from this Stackoverflow answer.