I have a small problem:
I am trying to send a variable from controller 1 to controller 2 and then send it back to controller 1. Here is the logic:
In order to start making a new model, the user has to sign in.
Guest users must also have access to the path of the new model.
If not logged in, guest users have to be redirected to sign in.
Once logged in, users have to go back to the path they previously tried to access.
I decided to pass the type of the model to the log in screen and then pass it back to the new model action.
The variable type usually comes from the route, so upon redirecting to the login screen, I just pass it over.
businesses_controller.rb:
before_filter :require_login
def require_login
unless current_user
redirect_to signin_path( :type => params[:type])
end
end
When I get redirected, the variable gets passed into my url - so far, so good:
/signin?type=ccompany
The problem comes when I try to grab the variable from the URL and use its value to redirect the successfully logged in user back to where he tried to go to:
sessions_controller.rb:
class SessionsController < ApplicationController
before_filter :initialize_type , :only => [:new , :create]
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
private
def initialize_type
#type = params[:type]
end
end
When I put the #type variable into my view, its value renders without problems. But when I try to log in, this is the error I get:
undefined method `new__path' for #<SessionsController:0x007f76189d42f8>
I also tried
redirect_to :controller => "businesses" , :action => "new" , :type => params[:type]
But the type symbol passes with no key. I also tried putting a variable there, still doesn't work.
So far, it has only worked when I just write down a string that matches the path, but that is not what I need. I need to pass the value of the params[:type] hash. I've put the value of the variable in the view and it's all good, but when I try to pass it into a function, its value somehow 'disappears'.
How can I make this work?
Problem is:
if defined?(:type)
it always returns true, as a symbol is always defined. What you want is:
if defined?(#type)
however this might add 'unintentional feature' to your code, as nil is well defined in ruby. You should do:
if #type
NOTE:
I need to pass the value of the :type symbol. - symbol has no value. Variables do have values, symbol is a symbol and it is its own value.
After understanding the question:
The above still holds.
You cannot pass instance variable from one action to another. For each request rails instantiate new controller instance and all instance variables are lost. There are number of ways to pass it though.
Create hidden field in your form to hold the value. You need to keep in mind that its value can be overwritten by any internet user with a minimal knowledge of how internet works.
In your new action simple write the value into a session and read it in you create action. Since session is either stored server side or stored in encrypted cookie, there is very small chance anyone can temper with it.
Ok, I found a workaround.It's a bit clumsy and may cause security issues, but it works. I forced the variable to pass as a session parameter.
I put the received variable as a hidden field in the new session form:
<%= form_for(:session , url: sessions_path ) do |f| %>
<%= f.label :email , t('session.email') %>
<%= f.text_field :email %>
<%= f.label :password , t('session.password') %>
<%= f.password_field :password%>
<%= f.hidden_field :val , value: #type %>
##type is set in the before filter as a variable passed from another controller
Then, upon submit, I just catched the parameter and put it into its place:
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
params.require(:session).permit(:val)
#type = params[:session][:val]
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
Now the function works properly. Unfortunately, I have to use the view as a "conduit" that passes a variable. I would be really happy if somebody writes a helper method for this or a better workaround.
Cheers.
Related
I'm trying to implement a form with the following behavior:
1) Input some semantic data about a user (i.e. username).
2) Do a User.find_by(:username = username).
3) If such a user exists, direct to show page for that user.
4) If such a user does not exist, create a new user with the provided username, then redirect to the show page for that user.
This should be simple but I cannot figure out how to format the form_for helper and my show and create actions in my user_controller to implement this behavior.
I currently have:
form_with :url => 'users/:id', :method => :get do
...
end
because I'm ultimately trying to invoke the "show" method of the controller. However, my form does not take in a user's id as a parameter, and when the user does not yet exist there is no :id parameter to access at the time of the form's submission.
How can I set up my form to redirect to show in each case, while still adhering to the logic explained above?
You can do something like this in your User's Controller create action
def create
usr_name = params[:username]
#user = User.where(username: usr_name).first_or_initialize
if #user.persisted?
redirect_to user_path(#user) # or whatever your user show path is
elsif #user.save
redirect_to user_path(#user)
else
render :new
end
end
You would just need to make sure that you are validating the uniqueness of usernames.
Also, first_or_initialize(and its counterpart first_or_create) can take in a block. So, you can assign other attributes to the new User like this...
User.where(username: usr_name).first_or_initialize do |usr|
usr.some_attribute = some_value
end
you can use find_or_initialize_by(unique_key) in your create method.unique_key can be any key which you are using to identify your user such as email,phone etc.
I am working on a reservation project and after login I want to pass the current user information from the Sessions Controller to a Reservations Controller via a home page. How can I do that? I have been following Michael Hartl's Rails Tutorial for the login. Here's what I have tried in the create section of the Sessions Controller
render home_path :local_variables[:id => user_id]
and in the home_path(users#home)
<%= link_to new_reservation_path(:user_id => :id) %>
but it shows me an undefined method error. My Reservation model has a user_id column.I am confused regarding this matter. What should I do?
render home_path :local_variables[:id => user_id]
Seems weird to me to pass locals that way (don't even know if it's possible, never seen locals used outside of rendering views/partials).
I think the best way is to redirect instead and set the user in the sessions once they have been logged in successfully, so in your login action:
user = User.find_by_email(params[:user][:email]) # or however you are finding the person trying to login
session[:user] = user
redirect_to home_path
then in users#home
#user = session[:user]
and finally, in the view:
<%= link_to new_reservation_path(:user_id => #user.id) %>
EDIT
Actually, probably not a good idea to store an object in the session, instead of
session[:user] = user
You could try:
session[:user_id] = user.id
and then to find the user:
#user = User.find(session[:user_id])
If you still get an undefined error then it's probably because the user is nil (unless the User model actually has no id attribute, which would be strange) so there might be an issue with the login part, it's hard to say without knowing what that looks like.
If you need the logged in user on every page, you could abstract the functionality out into the application controller:
before_filter :check_user
def check_user
#user = User.find(session[:user_id]) if session[:user_id]
end
Then, you can use the #user instance variable anywhere in your app.
I am trying to figure out the best way to do the following (there are a few ways I can think of, but I want to know what the best way to handle it is):
A user is putting together a shipment, and then clicks the "Send" link, which sends him to the /shipments/:id/confirm page. The confirm action checks to see if the user has a completed ShippingAddress; if not, it sends him to the ShippingAddress#new. (If he does, it render the confirm page.
I want the user to be able to complete the ShippingAddress#new page, submit it, and then be redirect back to the /shipments/:id/confirm. How can I do that? How can I pass the :id to the ShippingAddress#new page without doing something like redirect_to new_shipping_address_path(shipment_id: #shipment.id) in the Shipment#confirm action? Or is that the best way to do that?
class ShipmentsController < ApplicationController
def confirm
#shipment = Shipment.where(id: params[:id]).first
unless current_user.has_a_shipping_address?
# Trying to avoid having a query string, but right now would do the below:
# in reality, there's a bit more logic in my controller, handling the cases
# where i should redirect to the CardProfiles instead, or where I don't pass the
# shipment_id, and instead use the default shipment.
redirect_to new_shipping_address_path(shipment_id: #shipment.id)
end
end
end
class ShippingAddressesController < ApplicationController
def new
#shipment = Shipment.where(id: params[:shipment_id]).first
end
def create
#shipment = Shipment.where(id: params[:shipment_id]).first
redirect_to confirm_shipment_path(#shipment)
end
end
[In reality, there is also a CardProfiles#new page that needs to be filled out after the shipping address is].
Try calling render instead of redirect_to, and set the id into an instance variable. Adjust the view logic to pull that instance variable if it exists.
#shipment_id = #shipment.id
render new_shipping_address_path
In the view
<%= form_for #shipment_address do |f| %>
<% if #shipment_id %>
<%= hidden_field_tag :shipment_id, #shipment_id %>
<% end %>
I don't know your view logic entirely, but giving an example.
I am in chapter 8 of the book, where we are trying to implement the signup functionality for the sample app with actions "new" and "create". Here is my questions about these 2 methods/actions,
The "new" action/method is defined as below in the User controller
class UsersController < ApplicationController
.
.
.
def new
#user = User.new
#title = "Sign up"
end
end
here the #user is defined so that its is accessible in the form of the html page for signup. As soon as the user hits signup button the "create" action/method of the user controller gets called, the code for the create action/method is below,
class UsersController < ApplicationController
.
.
.
def create
#user = User.new(params[:user])
if #user.save
# Handle a successful save.
else
#title = "Sign up"
render 'new'
end
end
end
Here is my question,
why are we calling "User.new" twice once inside the "new'method/action and inside "create" method/action" ?
Thanks for the reply,
what if I implement the create method like the one below, I have removed the second call to new, Is this wrong. if so what is wrong ?
def create
if #user.save
# Handle a successful save.
else
#title = "Sign up"
render 'new'
end
end"
Thanks
If you are using the form_for implementation on the erb view.
This uses the #user object to associate the fields with the objects attributes.
This fields will be passed as key value pairs.
<%= form_for #user do |u| %>
<%= f.text_field :name %><br />
<%= f.text_field :age %><br />
<%= f.submit %>
<% end %>
For this you create a blank user object in the new method.
In the create method you create the object with the params submitted.
This helps you to create a User object directly from the parameters, and validate and save the object.
#user = User.new(params[:user])
After the submission of the form, the request params are passed to with the key as user object attributes.
The first time User.new is called, you are creating a model in memory that is used to generate the fields to populate the new user view. That html page then gets returned to the client, and the server forgets all about it. When the client fills out the form and commits it back to the server the create method gets called on the controller. The first thing the controller does is make a new User model, and populate it with the parameters. Until then, nothing has been persisted which is why the new method gets called twice
The first new in the new action is needed to get an empty object, which later is used in the user form in the view, so that Rails form helpers can determine the form object and have something to get the information Rails needs to automatically set all the default values of the form (like the default url to your UserController). With this information the form and page are rendewred and Rails forgets about it. (If the model has default values for some attributes, those will be set too and would appear in the form)
Now you have the form in your browser, fill in the values and submit it. This submit is handled by the create action and here the second new creates an object and fills it with the values submitted from your form and now available in the params hash. This object will have values and the #user.save call will save them to the database if they pass validation (result true). If there are errors, like missing data in mandatory fields, the save will fail and the form from the 'new' view will be rendered again. This time with the data in the object that was created, so all valid data will be filled in the input fields.
Say I have a site like this (generic Q&A site) in Rails and I wanted this "ask" page w/ a text box to be the first page a user sees, even if he's not logged in. He enters a question, and on the 'new' method I check that he's not logged in, and bounced him to /session/new, where he can either log in or create a new account. Question is, how do I (and what is the best way to) preserve that question that he initially asked all through this process?
I'm understanding the flow of action described in the question to be
user is presented with a form
user is redirected to log in page on submit
user is redirected back to form on successful log in
repopulate form on load (Question asks how to do this step)
user finally submits their form.
With steps 2-4 omitted if the user is logged in.
I'm sorry, but I see your question more as a symptom of an underlying UI issue than a rails question.
If only logged in users can post questions, then why display the text box?
If a user is going to have log in any way, why not get that out of the way first. An even better solution is to integrate the log in and form.
Something like this in the view:
<% form_for :question do |form| %>
<% unless logged_in? %>
<% fields_for :session do |session_form|%>
<%= session_form.label :login %>
<%= session_form.text_field :login %>
<%= session_form.label :password %>
<%= session_form.password_field :password %>
<%end%>
<%end%>
<%= form.text_area :question %>
<%end%>
And in the controller
def new
...
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
end
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
if logged_in? && #question.save
.... process successful entry
else
... process unsuccessful entry
end
end
Edit: Mohamad's raises the question of reusing this pattern across multiple controllers and forms. So the answer was updated to address reuse of this pattern.
To simplify this for reuse, you could put this block in a helper function that is referenced in the before_filter for actions that require it.
def login
unless params[:session].nil?
self.current_user = User.authenticate(params[:session][:login], params[:session][:password])
if logged_in?
flash[:notice] = "Logged in successfully"
else
flash[:error] = "Incorrect username and or password."
end
end
end
as in:
before_filter :login => :only [:new , :edit, :update, :delete]
On the view side, it shouldn't be too hard to construct a new variant of form_for that embeds the session parameters. Maybe form_for_with_session?
As for handling an unsuccessful response, I would suggest helper function that takes a block of code. Sorry I don't have time to write out or test one for you.
You keep it in the session. So after logging in, when the user goes back to asking his question, you see there's already something in session.
And you can directly display it.
def create
if current_user # Implement this method in your auth framework
#question = Question.new(params[:question] || session.delete[:question])
# (the usual stuff you'd do to save)
else
session[:question] = params[:question]
redirect_to :controller => :sessions, :action => "new"
end
end
Then, after your user creation and authentication stuff is all done in your login action, just make sure you POST back to this create action if session[:question] is defined.