Rails: Can you pass arguments to the after_create method? - ruby-on-rails

In my application, only users with the administrator role may create new users. In the new user form, I have a select box for each of the available roles that may be assigned to new users.
I am hoping to use the after_create callback method to assign the role to the user. How can I access the selected value of the select box in the after_create method?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end
In the user model I have:
after_create :assign_roles
def assign_roles
self.has_role! 'owner', self
# self.has_role! params[:role]
end
I receive an error because the model doesn't know what role is.

You could use attr_accessor to create a virtual attribute and set that attribute as part of your create action.

The short answer is, no. You cannot pass any arguments to after_create.
However what your trying to do is a pretty common and there are other ways around it.
Most of them involve assigning the relationship before the object in question is created, and let ActiveRecord take care of everything.
The easiest way to accomplish that depends on the relationship between Roles and Users. If is a one to many (each user has one role) then have your users belong to a role and sent role_id through the form.
<%= f.collection_select :role_id, Role.all, :id, :name %>
If there is a many to many relationship between users and roles you achieve the same by assigning to #user.role_ids
<%= f.collection_select :role_ids, Role,all, :id, :name, {}, :multiple => :true %>
The controller in either case looks like
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
flash[:notice] = 'User creation successful.'
format.html { redirect_to #user }
else
format.html { render :action => 'new' }
end
end
end

Related

How do I update attributes of a nested model?

I have a company model, that accepts_nested_attributes_for :users and my controller looks like this:
def create
#company = Company.new(company_params)
if #company.save
redirect_to root_url
else
render 'new'
end
end
private
def company_params
params.require(:company).permit(:name, :company_size , users_attributes: [:id, :name])
end
what I'd like to do is set the admin boolean I have in user to true.
Essentially what I'm doing is making a user sign up by creating a company, and also registering their user, and thus making the person registering the company an admin.
You can simply alter the User object before it is saved.
def create
#company = Company.new(company_params)
#company.user.admin = true
if #company.save
redirect_to root_url
else
render 'new'
end
end
You could also do this as a a model callback. However your implementation might be a little to naive. What happens if a user belongs to several companies?

rendering the partials in controller after the validation check

I have two partial views for two different sign up forms. On my home page , based on the link one clicks on, I'm rendering respective form.(views/application/index)
= link_to 'Mentor', new_user_path(user_role: true), :class =>'btn'
= link_to 'Mentee', new_user_path, :class =>'btn'
In views/users/new.html.haml , I'm checking the user role and redirecting to the respective form.
- if params[:user_role]
= render 'mentor'
- else
= render 'mentee'
In the user model I've added validation like this.
class User < ActiveRecord::Base
email_regex = /\A[\w+\-.]+#cisco.com/i
validates :cisco_email, :presence => true,
:format => { :with => email_regex,}
validates :work_city, :presence => true
end
So, when there is any invalid field I want to direct to the same form with a flash message. My controller looks like this.
class UsersController < ApplicationController
def index
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user]) # Not the final implementation!
if #user.save
flash[:success] = "Welcome to the CSG Mentoring Tool!"
redirect_to #user
else
flash[:notice] = "Error regsitering."
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
end
end
end
When an invalid field entry is there, it is redirecting to 'mentee' page no matter on which page the error is made. Also the entire css styling gets changed and flash is also not displayed
Why this is not working?
if params[:user][:user_role]
render :partial => 'users/mentor'
else
render :partial => 'users/mentee'
end
params[:user][:user_role] is nil.
You can check it using lots of way:
Above your if condition raise params[:user].inspect
Why its nil?
Reason of this is You are passing new_user_path(user_role: true) user_role true, but user_role is not true in mentor form.
params[:user_role] will not set user_role = true field in mentor form.
Set user_role
<%=f.hidden_field :user_role, value: params[:user_role] %>
If its supposed to be true for mentor always
<%=f.hidden_field :user_role, value: true %>
By default flash will make them available to the next request, but sometimes you may want to access those values in the same request.
Reference
This works with redirection
flash[:success] = "Welcome to the CSG Mentoring Tool!"
This will work with render
flash.now[:success] = "Welcome to the CSG Mentoring Tool!"

Nested form validation rails 3.2

I have a job and user(devise) form in the same view. When I am trying to submit with errors in the user fields it gives me an exception page with the validation messages. Submitting errors in the job fields works fine!
job_controller.rb
def new
#job = Job.new
if !current_user
#job.user = User.new
end
respond_to do |format|
format.html # new.html.erb
end
end
def create
#types = Type.all
#categories = Category.all
#job = Job.new(params[:job])
#if not logged in creates a user and sign in
if !current_user
#user = User.new(params[:job][:user_attributes])
else
#user = current_user
end
#job.user_id = #user.id
respond_to do |format|
if #job.save
if !current_user
sign_in(:user, #user)
end
format.html { redirect_to #job }
else
format.html { render action: "new" }
end
end
end
job.rb
attr_accessible :user_attributes, :description, :name ....
belongs_to :user
accepts_nested_attributes_for :user
Thanks!
That becuase you are calling, #user.save! which will generate an exception. Also doing it this way won't put the job in the same transaction as User. What you want are nested_attributes:
class Job < ActiveRecord::Base
accepts_nested_attributes_for :user
end
If the user is logged in, don't show that part of the form and filter those params.
See more in the Rails documentation here http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
EDIT:
Simplify your controller code, since you're using nested attributes you no longer need to manually create a user.
#if not logged in creates a user and sign in
if !current_user
#user = User.new(params[:job][:user_attributes]) # this is no longer needed
else
#user = current_user
end
#job.user_id = #user.id # this is redundant
To something more like:
# if logged in, manually assign the user (also you may want to reject any user attributes)
#job.user = current_user if current_user

What happen when using as admin?

In Rails 3 in action its based on 3.1 so its kind old because i am using 3.2.12 when i tried to assign admin without make it free for mass assign it raises an error and thats because the differences between 3.1 and 3.2 as the author said.
so its better to use which on of the following and what is the difference ?
first method in controller
def create
#user = User.new(params[:user], :as => :admin)
if #user.save
flash[:notice] = "User has been created."
redirect_to admin_users_path
else
flash[:alert] = "User has not been created."
render :action => "new"
end
end
and in model
attr_accessible :email, :password, :admin, :as => :admin
second method in controller
def create
#user = User.new(params[:user], :without_protection => true)
#user.admin = params[:user][:admin] == "1"
if #user.save
flash[:notice] = "User has been created."
redirect_to admin_users_path
else
flash[:alert] = "User has not been created."
render :action => "new"
end
end
without adding the line above in the model
which one will protect from mass-assign or both are free ?
Both attr_accessible and :without_protection => true can be used to allow mass assignment on attributes of a model they are defined in.
so its better to use which on of the following and what is the difference ?
To answer this concern, I think using attr_accessible is better because you define exactly which attributes you want to allow for mass assignment compared to :without_protection => true which opens up all attributes in your model to be mass assigned.
Usually, passing the :without_protection => true is okay if you know exactly what the user input is, for e.g. when seeding data. But for inputs that come from a form (user input) you want to specify exactly what is allowed for mass assignment.
Hope this helps.
Update:
In the following statement, the as option you supply to attr_accessible confirms that the attributes email, password and admin are allowed only if the user is admin.
attr_accessible :email, :password, :admin, :as => :admin

RoR: How to modify the controller to accept update several params only?

Here is the code generated by rails:
def update
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
flash[:notice] = 'User was successfully updated.'
format.html { redirect_to(#user) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
But I don't want user to update the whole user, assume that my user have fname, lname and gender, instead of remove the gender from the view, I want to restrict that the update method ONLY accept fname and lname only, if he/she want to update the gender, I won't allow him/her to do so. How can I restrict the user to do so? thank you.
or add a custom #user.update_only() method, which makes it also easier to reuse in different contexts...
class User
def update_only(attrs = {}, *limit_to)
update_attributes(attrs.delete_if { |k,v| !limit_to.include?(k.to_sym) })
end
end
Then just do something along the lines of
#user.update_only(params[:user], :fname, :lname)
There are two methods in ActiveRecord that come in handy in cases like these, attr_protected and attr_accessible.
You use them like this:
class MyModel < ActiveRecord::Base
attr_accessible :fname, :lname #Allow mass-assignment
attr_protected :secret #Do not allow mass-assignment
end
model = MyModel.new(:fname => "Firstname", :lname => "Lastname", :secret => "haha")
puts model.fname # "Firstname"
puts model.lname # "Lastname"
puts model.secret = nil # Can not be set through mass-assignment
model.secret = "mysecret" # May only be assigned like this
puts model.secret # "mysecret"
However, if you only need this functionality at one place, then Salil's solution will work just as well.
One thing to note is that you should use attr_acessible to whitelist attributes that are OK to mass-assign, and make every other attribute protected. By doing so, you hinder mean people for updating data they are not supposed to touch.
See the docs for more info.
Use Hash parameters of the update_attributes
#user = User.find(params[:id])
#user.update_attributes(:fname=>params[:user][:fname], :lname=>params[:user][:lname])
You can delete unwanted attributes from the param[:user] Hash:
# ...
attributes = params[:user]
gender = attributes.delete :gender
raise SomeError unless gender.blank?
if #user.update_attributes(attributes)
# ...
end
# ...
This code removes :gender from the Hash and checks if it is filled in. If so, an exception is raised. Of course you could give a nice warning or silently ignore the fact that the gender was filled in.

Resources