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.region = params[:user][:region]
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.
Related
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
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]
controller show action
def show
#batch = Batch.find(params[:id])
#batch_id = #batch.id
authorize #batch
end
pundit policy
def show?
puts #batch_id
if !current_user.nil? && (current_user.role?('Student')) || (current_user.role?('Administrator')) || (current_user.role?('SupportSpecialist')) || (current_user.role?('Instructor'))
true
else
false
end
end
I am getting nil when I puts #batch_id, how can I get that value in policy action
Your controller:
def show
#batch = Batch.find(params[:id])
#batch_id = #batch.id
authorize #batch
end
Your policy file might be:
class BatchPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
true if record ... // record will be equal #batch
end
end
If you want additional context, then you must know:
Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need.
Pundit does not allow you to pass additional arguments to policies for precisely this reason.
There you can learn about it from the horse's mouth.
I implemented
gem 'devise_invitable'
for model User and I am facing an issue while inviting an existing user. The error says "USER IS ALREADY REGISTERED". I would like to add the same user in another User invited list. How can this be done?
For those looking for a different implementation of the same problem, you can add the new behavior to the InvitationsController protected method, invite_resource.
A more verbose explanation of the example below can be found on the DeviseInvitable wiki page, titled Invite a Resource (or User) that Has Already Signed Up without Invitation.
class Users::InvitationsController < Devise::InvitationsController
protected
# invite_resource is called when creating invitation
# should return an instance of resource class
# this is devise_invitable's implementation
# def invite_resource(&block)
# resource_class.invite!(invite_params, current_inviter, &block)
# end
def invite_resource(&block)
#user = User.find_by(email: invite_params[:email])
# #user is an instance or nil
if #user && #user.email != current_user.email
# invite! instance method returns a Mail::Message instance
#user.invite!(current_user)
# return the user instance to match expected return type
#user
else
# invite! class method returns invitable var, which is a User instance
resource_class.invite!(invite_params, current_inviter, &block)
end
end
end
To accomplish this, you'll need to create a new Invitations Controller that inherits from the original Devise::Invitations controller, but has modified logic in the create method.
The gem's README has a section on "Configuring Controllers" which describes this process. I also suggest having a look at the source code for the parent controller as it will help provide some context.
I did something similar to what you desire, and used the Rails built-in method of find_by_email. Here's some of the code I used...
def create
# new user
if User.find_by_email(invite_params[:email]).nil?
super
# existing user
else
#u = User.find_by_email!(invite_params[:email])
....more code that does what you want....
end
end
NOTE: Rails is smart and will use logic from the parent controller if no conflicting instructions are given the child controller you create. The point is that you don't need to re-write the whole controller. Ideally, you'll just make your modifications in the child controller then call super to revert back to the same method in the parent controller to finish the action.
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.