Redirect_to not sending object - ruby-on-rails

As part of a logging in, I redirect back to the original controller and action if the User doesn't save. But the original controller isn't receiving the resource object.
In registrations_controller.rb
redirect_to m_signup_after_job_post_path(resource, job: params[:job_id])
In pages_controller.rb
def signup_after_job_post
resource ||= User.new
respond_with(resource)
end

Although the helpers like something_path take the object itself, internally they just call the .id method of that object and pass only the ID into the route itself. So in the action that receives that route you will always need to do something like:
resource = User.find params[:id]

Related

Rails before_action with external authorization and callback

For my Users Controller, I'd like to always load in the user as the #user instance variable. This is done by loading in the user from an external authorization link with a callback.
Here is a minimal example:
class UsersController < ApplicationController
before_action :authorize_user
skip_before_action(:authorize_user, { :only => [:auth_callback] })
# this is my before action meant to load in the #user instance variable
def authorize_user
# if user is not stored in session, get authorization from external link
if !session.key?(:user_hash)
redirect_to(external_auth_link)
# otherwise, loads user from hash
else
user_hash = session[:user_hash]
#user = get_user_from_hash(user_hash)
end
end
# this is the callback from the external authorization link
def auth_callback
user = get_user_from_callback()
session[:user_hash] = user.to_hash
# the line in question
# should I redirect here or what?
end
def page1
# some page using the #user instance variable
render({:template => "users/page1.html.erb"})
end
def page2
# another page using the #user instance variable
render({:template => "users/page2.html.erb"})
end
end
When calling page1 or page2, I'd like the before_action to properly load in the #user instance variable, then return to the appropriate method.
Currently, the before_action works fine when user_hash is already stored in session.
However, when user_hash is not saved in session, it redirects the the external authorization, then goes to the callback, then doesn't know where to go after. It wants me to redirect or render something. How could I get it to redirect to the action (page1 or page2) that was originally called?
Ex: If I call page1 when user_hash is not stored in session it will go to:
authorize_user
The external authorization
auth_callback
page1
I've tried to remove the code unrelated to this issue, but let me know if anything else would be helpful. Thanks for any suggestions.
If possible, try getting an additional parameter from the service that handles the auth and redirects to auth_callback. That additional parameter can tell you where to redirect.
If not possible, then you can save the current action to session in authorize_user.
def authorize_user
# if user is not stored in session, get authorization from external link
if !session.key?(:user_hash)
# save action to session
session[:redirect_to] = action_name
redirect_to(external_auth_link)
# otherwise, loads user from hash
else
user_hash = session[:user_hash]
#user = get_user_from_hash(user_hash)
end
end
Then use it to redirect in callback.
See this topic on getting action name in rails.
Alternatively, you can save the full route or path to the session as well.
Please bear in mind that storing this in session is not the best choice, try getting an additional parameter from auth handler if you can.

Add params to after_sign_in_path_for

I'd like to add parameters in the path returned in my after_sign_in_path_for function.
When a user is not authenticated and submits a form, i store the params and my website redirects him to the sign in form.
def create
if current_user.nil?
session[:form_data] = params
redirect_to new_user_registration_path
else
# here I handle form params
end
end
Then the user logs in and here is my after_sign_in_path_for function.
def after_sign_in_path_for(resource)
if session[:form_data].present?
'/transactions#create'
else
session[:previous_url] || root_path
end
end
If session[:form_data] exists, i would like to redirect him to transactions#create, but with session[:form_data] contents as parameters.
I tried to use redirect_to but it throws an exception since the devise function calling after_sign_in_path_for also calls redirect_to.
Is there any way to do that ?
The question here is if you should - redirects are GET requests. Your create action should only respond to a POST request since a GET request is stored in the browser history and may be inadvertently repeated.
What you could do instead is redirect to transactions#new and use the params to pre-fill the form:
def new
#transaction = Transaction.new(new_transaction_params)
end
def new_transaction_params
params.fetch(:transaction, {})
.permit(:a, :b, :c)
end
def after_sign_in_path_for(resource)
if session[:form_data].present?
new_transaction_path(query: session[:form_data])
else
session[:previous_url] || root_path
end
end
But if you have saved the form data in the session anyways there is no need to pass it via the params. In fact sending parameters in the query string is somewhat less secure.
This assumes that you have setup the routes with:
resources :transactions

Alternative for :id param

I have resources :users and custom_id inside users table.
I want to use link_to "user", user method choosing custom_id to provide link with this field as param.
My show action inside users_controller.rb:
...
#user = User.find(custom_id: params[:id])
...
In Your form You could use:
<%= link_to #user.name, #user %>
because You already wrote in UsersController:
...
#user = User.find(custom_id: params[:id])
...
A better way to manage this is via the to_param method which you can define inside a model. When you pass a model instance into a path helper, it calls this method on it, which by default returns the id. You could override it to use custom_id instead.
#in User class
def to_param
self.custom_id
end
Now you can say
link_to "user", user_path(#user)
and it will generate html like
user
and so #user.custom_id will come through in params[:id].
You will still need to make sure that you load the user via the custom id field, rather than the id, in the controller. A common pattern is to put it into a protected method in the controller:
#in UsersController
protected
def load_user
#user = User.find(custom_id: params[:id])
end
now you can say load_user in all the actions which load #user, or put it into a before filter for the relevant actions, which is even DRYer.

Devise: Ability to pass parameters to the registrations#sign_up action

Once in a while we send customized registration links to our leads. The link contains parameters than can be used to pre-fill the registration form.
http://www.example.com/users/sign_up?user[company_name]=Foo&user[region]=NA
Our registration form has fields for accepting company name and region. Which can be pre-filled based on the registration link.
This should work in practice, but it doesn't due to how the registrations#new action is implemented. The new action calls the build_resource method with an empty hash.
def new
resource = build_resource({})
respond_with resource
end
The build_resource method ignores the resource_params when the input is non nil.
def build_resource(hash=nil)
hash ||= resource_params || {}
self.resource = resource_class.new_with_session(hash, session)
end
I had to over-ride the the new action in my registrations controller to overcome this issue. I don't like my solution as it is brittle.
def new
resource = build_resource
respond_with resource
end
Is there a reason why the new action is invoked with an empty hash? Can it be invoked with out empty hash(like in the create action)?
I ended up overriding build_resource and scoping the change to new action.
def build_resource(hash=nil)
# scope the change to new actions
return super unless action_name == "new"
super.tap do |user|
user.company_name = params[:user][:company_name]
user.reg‭ion = params[:user][:reg‭ion]
end
end
I believe this is the intended behaviour of the build_resource method. Similar to Model.new you can either pass a hash of initializing properties or nothing, resulting in a pre-filled and an empty model respectively.
If you want to make your controller action more explicit you could instead call build_resource(params[:user]) which should avoid the brittleness you're concerned about.

where does this param symbol come from in Rails session

I'm reading Rails 3 Way by Obie Fernandez. He's demonstrating the use of a plugin Authlogic, and created a User and UserSession model and a UsersController and UserSessionsController.
He hasn't created any views (but he might assume some exist)
In the UserSessionsController, he creates this code
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
redirect_to user_path(current_user)
else
render :action => :new
end
end
def destroy
current_user_session.destroy
redirect_to new_user_session_path
end
end
My question relates to the create method. When he writes
UserSession.new(params[:user_session])
where is :user_session coming from? I undersdtand that UserSession.new instantiates a new object, but where do the params come from? and what names would they have?
Does it depend on something in the imaginary view? or are these params automatically generated by Rails based on the name of the Models?
params is a special hash that is passed to all actions, regardless of the type. If a given action has no parameters, then it's simply empty. It's how you can pass parameters from a page/form/URL parameters into the action. One of the most common sources of parameters are data elements from a form.
In the case of authlogic, it contains user credentials for creating the user session (username, password).
Check out the parameters section for more information.

Resources