I want to simply save the token parameter I receive from the JSON request, but I simply cannot get to show if it is actually saved. I've observed that if you do a POST request with JSON parameters rails routes it to the create method. And I set my global variables attribute (in this case token), however when it redirects to the index.html.erb it gives me the error below
<h1>
NoMethodError in
Inits#show
</h1>
<p>
Showing <i>/Users/alioral/Desktop/Rails/testApp2/app/views/inits/show.html.erb</i> where line <b>#3</b> raised:
<pre><code>undefined method `token' for nil:NilClass</code></pre>
</p>
Here is my controller class;
class InitsController < ApplicationController
def index
end
def show
end
def create
#init=Init.new("token"=>params[:token])
#init.save
respond_to do |format|
format.html { redirect_to #init }
format.json { render json: #init }
end
end
end
And just in case here is the model I've generated (without using scaffold);
class Init < ActiveRecord::Base
attr_accessible :token
end
Here is my show.html.erb file;
<h1>Hello, World</h1>
<b>The token is:</b>
<%= #init.token%>
First, create does not redirect to index, it redirects to the new created #init path. Second, it looks like the show haml file contains a reference (in line 3) to #init.token, and since #init is nil you get this error.
The instance variables (the ones that starts with #) doesn't survive redirect. For every request a new instance of controller is made with the #init variable unset.
When you call render ... it's still with the original #init, but when you call redirect, then client issue a new HTTP request and on the server side there is new controller instantiated and your #init in the #show action is nil then.
update: So in your controller you should have something like this in your show method:
def show
#init = Init.find(params[:id])
end
Related
New to Ruby on Rails and been cracking my head on this. I have been following this tutorial here to make this form to save records into my DB - https://human-se.github.io/rails-demos-n-deets-2020/demo-resource-create/
This is my controller:
class ViewaddparamController < ActionController::Base
def view_add_param
newParam = ViewaddparamController.new
respond_to do |format|
format.html{ render :viewaddparam, locals: { newParam: newParam } }
end
end
def add_param
# new object from params
mlParam = ViewaddparamController.new(params.require(:Viewaddparam_Controller).permit(:name, :machine_model, :parameter_1, :parameter_2, :parameter_3, :data_train_set, :start_date, :end_date))
# respond_to block
respond_to do |format|
format.html do
if mlParam.save
# success message
flash[:success] = "ML parameters saved successfully"
# redirect to index
redirect_to model_url
else
# error message
flash.now[:error] = "Error: parameters could not be saved"
# render new
render :viewaddparam, locals: { newParam: newParam }
end
end
end
end
end
My route:
get 'viewparam', to: 'viewaddparam#view_add_param'
post 'add_param', to: 'viewaddparam#add_param', as: 'add_param'
My view:
<%= form_with model: newParam, url: add_param_path, method: :post, local: true, scope: :Viewaddparam_Controller do |f| %>
...
I kept getting this error whenever I try to submit the form
ArgumentError in ViewaddparamController#add_param
Wrong number of arguments (given 1, expected 0)
The error highlighted at my controller class, line 11.
What am I doing wrong here? I looked through the tutorial over and over but I can't see the fault here.
Thanks in advance.
It seems that you’re treating ViewaddparamController as both the controller – which in Rails terms is what responds to user requests - and the data model.
There’s often a one-to-one correlation to controllers and models, especially if you’re following what’s known as the RESTful pattern. So if your model was a Product, you might set it up in routes using a resources directive:
resources :products
That would set the app up to expect to use a ProductsController. And, using a similar coding style to your example, that would look a little like:
class ProductsController < ApplicationController
def new
product = Product.new
render :new, locals: { product: product }
end
def create
product = Product.new(params.require(:product).permit(…))
# etc
end
# etc
end
So you can see from this example that controller and model are related, but are named differently.
By using your controller name where you should be using the model, you’re calling #new on the controller, which is not normally something you need to do – controller instances are managed by the Rails app framework. And their initialiser doesn’t take any arguments, so Ruby complains that an initialiser that takes 0 arguments is being given 1.
On an unsuccessful save, I would like to redirect to the previous view but with the error message.
redirect_to user_path(#user_id), errors: #user.errors
but in the view, when I check for errors I get an undefined variable errors.
I am not using the same controller new and create, so I can't have #user.errors.any in new.html.erb. I have two different controllers, one in which form is there, and another controller which will take care of create, if the create is not happening I need to redirect to the previous controller.
You may need to use render instead of redirect_to.
Something like this:
# controller_1
def step_1
#user = User.new
#user.do_something
...
end
# controller_2
def step_2
if #user.save?
# redirect to another...
else
render 'controller_1/step_1`
end
end
Then on view step_1.html.erb, you can print out errors of #user with #user.errors.
You have to pass the parameters inside the redirect_to helper like below,
redirect_to user_path(id: #user_id, error: #user.errors.messages)
Please check the rake routes and pass the appropriate key for id, whether it's :id, or :user_id
I have a link that goes to an action, so if someone clicks:
localhost/cart/checkout?pid=123
It goes to the CartController checkout action which then displays a form.
But in some circumstances (depending on when I load the Product with id 123) I may not need to display the form, I can just load the data and then post to the form's action.
How can I programatically post to where my form was going to post with data.
class CartController < ApplicationController
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
# no need to display view, just post to handleCheckout
end
end
# checkout form posts to this action
def handleCheckout
end
end
I have not done something like this before but I have some idea so please note that none of the is tested.
If your handleCheckout action is meant to be used as a Get request then you can redirect to this action with the params. like:
class CartController < ApplicationController
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
redirect_to action: "handleCheckout", params: params
# Not sure whether you will get it as 'params' or params[:params] in handleCheckout action
end
end
# checkout form posts to this action
def handleCheckout
end
end
And if handleCheckout is meant to be used as post Then above method might not work since redirect_to will create a new http Get request to that action. so you may try something like this:
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
handleCheckout
# params since is a global hash and above method has access to it
end
end
# checkout form posts to this action
def handleCheckout
# your other code
redirect_to 'some_action' and return
# in above line you have to return with a render or redirect
# Otherwise it will render 'checkout' template with render and redirect or
# it will throw double render error if you have a simple render or redirect without explicit return
end
As I mentioned, I have not tried any of above code. There might be errors. I hope it helps.
When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.
OK, as is often the case, I have a controller action in my app that is protected from unauthorized access by a before_filter. The only thing is that I need to redirect this action if another condition is true:
class Payment < ApplicationController
before_filter login_required
def new
redirect_to some_other_path if #order.is_free?
#payment = Payment.new
end
end
In my testing, I check to make sure that the action is correctly protected, however, it is also the case that the #order.is_free statement is true. When this is the case, I get the following error:
`render_with_no_layout': Can only render or redirect once per action
Is there any way of checking to make sure I'm not already redirecting or to override an existing redirect?
I am assuming that the login_required method performs a redirect if the user is not logged in. In which case:
Your before filter should return false after calling redirect. This will prevent the new action from ever being called. Later versions of rails automatically do this if you call render or redirect in a before_filter, so maybe you are using an older version.
Also you should return after the call to redirect in the new handler, unless you want to always create a new Payment object.
Your class should be PaymentController, not Payment. The reason for this is so the controller class and model class do not clash.
I don't think your before filter is what causes the double render error. Take a look at this example:
class PostsController < ApplicationController
before_filter :perform_a_redirect, :except => [:wtf]
def index
redirect_to 'http://google.com'
end
def wtf
render :text => 'wtf'
end
private
def perform_a_redirect
redirect_to :action => 'wtf'
end
end
When visiting /posts, I get redirected to /posts/wtf. No double render error. Assuming your 'login_required' method only redirects/renders once, I suspect that the code you are posting here is not the problem, but that something else is causing this.
The before filter is a red herring. When #order_is_free? the code is both setting up a redirect and falling through to a rendering of new. The redirect statement doesn't control the flow of the method. Add a return statement after the redirect, or you can even return the redirect, as in return(redirect_to :action => :show, :id => #order, :controller => :free_orders)