When each row in a table is going to hold a person, i.e. it’s going to need a column for email
, password_digest
, etc; then you probably shouldn’t use the draft:resource
generator. This is because you do not want to generate the standard RCAVs (index
, show
, etc) with the standard forms to add a new record, update a record, delete a record, etc.
Instead, you need routes, actions, and templates for signing up, signing in, editing profile, cancelling an account, etc. The RCAVs that the draft:resource
generator writes for you require so much modification to transform into those pages that it is easier to just start from scratch with the model
generator.
Fortunately, however, there is another generator that will give you a nice head start towards the boilerplate required for most sign-in/sign-out RCAVs: draft:account
. Let’s say you want a model called user
with columns first_name
, last_name
, and company_id
:
rails generate draft:account user first_name:string last_name:string company_id:integer
You should not include email
, password
, password_confirmation
, or password_digest
in the command. email
and password_digest
string columns will automatically be added to the table for you.
Explore the generated code. It added:
User
model file.rails db:migrate
.In addition, ApplicationController
, the draft:account
generator wrote a method called load_current_user
:
# app/controllers/application_controller.rb
def load_current_user
the_id = session.fetch(:user_id)
@current_user = User.where({ :id => the_id }).at(0)
end
You can call this method from within any action to easily define an instance variable called @current_user
which would use the cookie created by the sign-in/sign-up actions to look up the record in the User
table that represents whoever is signed in.
Furthermore, it wrote a line:
# app/controllers/application_controller.rb
before_action(:load_current_user)
This line automatically calls the load_current_user
method before any action is called! That means that @current_user
is automatically defined within every action and view template. If someone is signed in, it is a User
; if not, it is nil
. From there on out, it’s up to you to use @current_user
to personalize the action/view.
The draft:account
also writes another method within ApplicationController
:
# app/controllers/application_controller.rb
def force_user_sign_in
if @current_user == nil
redirect_to("/user_sign_in", { :notice => "You have to sign in first." })
end
end
This method, if called, will redirect the user to the sign in page if they are not already signed in. This is a very common pattern in applications, and saves us the trouble of having to do a million if @current_user != nil ...
all over the place before we can use @current_user
safely (assuming we need a User
).
To call this method before every action, add this line to ApplicationController
:
# app/controllers/application_controller.rb
# Below load_current_user
before_action(:force_user_sign_in)
The generator writes this line, but starts it out commented; uncomment it if you want to opt-in to forcing users to sign in.
However, you need to allow them to visit the sign-in/sign-up pages while being signed-out; to do that, in UserAuthenticationController
, uncomment the following line:
# app/controllers/user_authentication_controller.rb
skip_before_action(:force_user_sign_in, { :only => [:sign_up_form, :create, :sign_in_form, :create_cookie]
Now you’re all set; a person will have to sign in before they can visit any other action in the application, and you can confidently use @current_user
knowing that it will actually be a User
and not nil
.
That’s it! You now have a solid sign-in/sign-out system to work from.
There’s no magic to this generator; all it did was automate the tedium of building the many RCAVs required for sign-up/sign-in/sign-out. Since the RCAVs for authentication are so boilerplate, in the real world we often automate them (or use a gem like Devise).
From here on out, it’s up to you to use @current_user
to make the app do what it needs to do.