Rails: Render actions with a specific path - ruby-on-rails-3

In my Rails3 application I add a specific match for edit my Post model as:
match '/edit' => 'posts#edit', :as => 'post_edit'
So, I overwrite the default path of the edit action. - Now if any errors occurs in the update action, it will render the edit action with settting the URL path with the default edit path /posts/1.
How can I overwrite that to render the edit action with setting the URL path as /edit instead of posts/1.

You are actually seeing the URL for "update", the default path for edit would be /posts/1/edit. I don't think you will be able to change what the URL displays using render :action. An alternate, although somewhat sloppy, method would be to redirect and save the #post object in the session or flash. If you do not save the #post object, you will lose the error messages from the update.
if #post.update_attributes(params[:post])
#business as usual
else
session[:post] = #post
redirect_to post_edit(#post)
end
Note that it is bad practice to save the whole object in the session (especially large objects), so you may instead want to only send the error message string with the flash and render that in the view. These are both rather hackish methods, but I don't really see an elegant way to do this.
Out of curiosity, why do you want to change the default URL? Is it necessary?

Related

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.

Creating an action inside a controller, after it has been generated

I am working on a rails app, and have generated a Controller via
rails g controller Pics index upload
but now I would like to add another action
delete
do I do this by manually adding a delete method in the Pics controller?
Or do I need to run another generation. My concern is that by adding manually something may not get included (not sure what it would be, but something under the hood.)
Is this the best way of adding a new action to a generated controller?
If you add manually, just make sure you have the right route on your routes.rb.
Let's say you create your delete action inside your Pics controller.
def delete
# do stuff
end
On your routes.rb, you need to append the route to your resource like this, remembering to analyse if it is a resource that acts upon a member of your resource, or a collection. (More about this you can read on this guide http://guides.rubyonrails.org/routing.html#adding-more-restful-actions).
resource :pics do
collection do
post :delete
end
end
Or
resource :pics do
member do
post :delete
end
end
Remember that all RESTFUL actions are handled by default by the rails router, again, try to read the guide i showed earlier for precise information about the topic. Hope it helps.

Url missing action after model failure

I have a profile controller action UPDATE which updates a user's account information. If the model is deemed invalid, I want to render my EDIT action like this:
if #user.valid?
#update
else
render 'edit'
end
The URL for the EDIT action is /my_profile/edit. However, when the render 'edit' code is processed upon failure, even though the correct view is displayed with appropriate errors, the URL that is loaded is /my_profile.
When I'm editing a profile on /my_profile/edit, how can I get the URL /my_profile/edit to be loaded when I call the EDIT action upon model failure?
Here are the current, applicable routes:
get 'my_profile' => 'my_profile#show'
get 'my_profile/edit' => 'my_profile#edit', as: 'edit_my_profile'
put 'my_profile' => 'my_profile#update'
Info: I'm also a beginner!
I thought "render" means, that only the view 'edit' will be rendered.
If you wanted a new request (which changes your also your url and goes in your edit action in the controller) you need an
redirect_to 'edit'
or am I on the wrong way?

rspec render_views ignores layouts? want to test static cached page does not display flash message

I'm trying to test that "static" pages (they're ERB, but get cached), generated through rails, don't render any stray flash notices left over by the authentication system (Devise) or wherever else.
I've tried writing this controller spec, but it appears that response.body only renders the template, not its layouts?
describe "so that static caching can be used" do
render_views
specify "flash notices are not rendered" do
# edit: the following flash lines don't do anything
# it's not the right flash object, this one is intended for
# inspecting after request not setting before request
flash[:notice] = "flash boo"
flash[:error] = "flash boo"
flash[:alert] = "flash boo"
get :show, :page => 'privacy_policy'
response.body.should have_content('flash boo')
end
end
class StaticPagesController < ApplicationController
layout 'master'
def show
response.headers['Cache-Control'] = "public, max-age=#{6.hours}"
render "static_pages/#{params[:page]}"
end
end
I've tried changing to a layout which does render flash notices, and even inserting the text into the layout template, but can't make the spec fail.
Is there a way to ask rspec to render the template with the appropriate layouts as well?
Is a controller spec the wrong way to try and do this?
It seems out of place, as it's more to do with which layouts are being used, and their contents, but the rendering process starts at the controller, it receives the result, and I can manipulate the flash hash contents before rendering.
Versions:
rails (3.0.10),
rspec-rails (2.6.1),
rspec-core (2.6.4)
Thanks, Nick
Turns out this isn't the right approach. It should be an integration test (cucumber, request spec, etc) as it's testing several layers. That and rails doesn't seem to render the templates inside their layouts at this level.
So in integration test:
Set up a flash message by making a request to a controller which does nothing else (create a dummy controller & routing in your test code), then hit the page of concern and make sure the flash notice isn't rendered.
e.g. https://gist.github.com/1178383
It seems like a long way round but it will cover it.

Internal Redirects with Rails 3

I am trying to implement a generic vanity url system in Rails 3. Generic in the sense that the vanity url isn't tied to a specific model. It is similar to the Vanities gem where I have a VanityUrlController that is hit from all the vanity urls. The difference is that I don't want to do an external redirect from foo.com/username to foo.com/users/1 or foo.com/product-name to foo.com/products/1. I want the vanity url to to stick around and have the the VanityUrlContoller do an internal redirect that mimics the corresponding show action.
I know what controller and action I want to dispatch the internal redirect to, but I am having problems with actually dispatching it. This is where I am at the moment:
TargetController.new.process("show", request.env)
It seems to start processing the new "request," but there are key pieces missing... like the actual request object.
Any thoughts or pointers would be very appreciated.
Update:
I ran across the dispatch method in ActionController which seems to get me a little farther.
TargetController.new.dispatch("show", request)
I have two problems with this, 1) it is listed as a private api method so if there is another way to do this, I'd rather that, and 2) even though it is rendering the show template for the TargetController, it is complaining about "Missing template vanity_urls/show."
UPDATE
Here is the basics of the solution we came up with. We do some other things like forcing encodings and checking some other application specific stuff, but this should be all you need to get going.
This goes at the very bottom of your routes.rb file so your vanity routes don't clobber your other named routes.
# Vanity routes.
match ':id', :as => 'vanity', :to => proc { |env|
id = env["action_dispatch.request.path_parameters"][:id]
vain_object = <method to find the object you want to display>
if vain_object.nil?
# render your 404 page
'application#404'
else
model = vain_object.class.model_name
# figure out the controller you want to go to
controller = [model.pluralize.camelize,"Controller"].join.constantize
# reset the :id parameter with the id of the object to be displayed
env["action_dispatch.request.path_parameters"][:id] = vain_object.id
# do your internal redirect
controller.action("show").call(env)
end
}
You'll also want to be careful while creating your vanity routes, so they don't collide with your other controllers. Some other useful things to know about are:
Rails.application.routes.routes.any? { |r| r.requirements[:controller] == vanity_url }
Which tells you if your vanity_url has the same name as a current controller.
Rails.application.routes.recognize_path("/#{vanity_url}", :method => :get)
Which tells you if this maps to anything already.
Sure, there are a couple of hacks along the way, but it works like a charm.
Try the FriendlyId plugin. It seems to do exactly what you want. Video tutorial:
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid