Unable to fully log out - RailsTutorial.org - Chapter 8 - ruby-on-rails

I'm working through the tutorial at Railstutorial.org and have completed chapter 8 with passing tests. My issue is that if I follow the guide's code exactly, I am able to log in, but unable to log out.
If I click "Log out" I am redirected to the root_path, but as a still logged in member.
I think I traced the behavior to my sessions helper. Specifically the following lines:
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
Through the rails console and pry I was able to determine that cookies[:remember_token] is nil, but since my user's remember_token is also nil, the find_by_remember_token is saying, "Hey nil == nil! Great! We found our user!" Except that this is obviously not desired behavior.
I've fixed it by changing the current_user method to the following:
def current_user
#current_user ||= cookies[:remember_token] && User.find_by_remember_token(cookies[:remember_token])
end
I completely accept and understand that this is likely an error in my code. I've found Michael Hartl's commit from this point in the project and compared our files and can't find a discrepancy. Any ideas on what might be going on here?
Thank you for your time.

put this in your...
Controller:
def logout
reset_session
redirect_to :controller => 'classified', :action => 'list'
end
and in your view: you need to add a logout link.
<% else %>
<p><%= "Welcome #{session[:user].login}!" -%></p>
<p><%= link_to 'Logout', :controller => 'user',:action => 'logout' -%></p>
<% end %>

Related

Unable to save merged articles

So I'm trying to create a feature for Typo (blogging app) that merges two articles in one. For some reason, I can't manage to save the merged article. I have followed several threads here, read over and over Rails and Ruby docs... And Can't figure out why it doesn't work
Besides finding what's wrong with my code, I'd like to know best solutions to see what's going on 'under the hood', to debug the code. Eg: See when methods are called, what parameters are passed...
Here is my code:
View:
<% if #article.id && #user_is_admin %>
<h4>Merge Articles</h4>
<%=form_tag :action => 'merge_with', :id => #article.id do %>
<%= label_tag 'merge_with', 'Article ID' %>
<%= text_field_tag 'merge_with' %>
<%= submit_tag 'Merge' %>
<% end %>
<% end %>
Controller
def merge_with
unless Profile.find(current_user.profile_id).label == "admin"
flash[:error] = _("You are not allowed to perform a merge action")
redirect_to :action => index
end
article = Article.find_by_id(params[:id])
debugger
if article.merge_with(params[:merge_with])
flash[:notice] = _("Articles successfully merged!")
redirect_to :action => :index
else
flash[:notice] = _("Articles couldn't be merged")
redirect_to :action => :edit, :id => params[:id]
end
end
Model:
def merge_with(other_article_id)
other_article = Article.find_by_id(other_article_id)
if not self.id or not other_article.id
return false
end
self.body = self.body + other_article.body
self.comments << other_article.comments
self.save!
other_article = Article.find_by_id(other_article_id)
other_article.destroy
end
Thanks in advance, and sorry if this is a rookie question :)
You did not mentioned what problem you are facing while saving, you just said you could not manage to save so I can't help you with that unless you provide some stack trace.
I will mention a few things though:
first is in your controller method you have multiple redirection code like redirect_to :action => index without any return from method so I think you will get multiple redirect or render error at some point like when unless executes and redirects but code continues the execution and throws error so try to reduce these redirects or mention it like redirect_to :action => index and return.
Then in model merge_with you are assigning other_article twice, you don't need the second one.
about debugging, you can create some puts line inside code and check it in rails server console to verify that the condition is executed like in controller method after if article.merge_with you can put:
puts "merge sucess"
and check console when merge action is called, if you see "merge sucess" then if block executed.
OR
use byebug like you used debugger. It will stop the execution where it will find the byebug word and will give access to a live session in rails console.
if you put it where you have debugger you can access the console and do the operations manually like run:
article.merge_with(params[:merge_with])
then see what happens. or put before self.save! in model and save it manually in console and check errors like self.errors.messages.
Stack trace is also helpful to see line by line code execution and identify the error.
I will update this if you post any info about what error you are facing

How to pass information from one controller to another via view rails

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.

Feedback on Admin set user as mod in rails

i'm a beginner to Rails and is following Michael Hartl's book. In chapter 9, where he set up an Admin role, and admin can delete users. I want to extend this feature by allowing admin to set users to Mod as well.
The plan is this:
1.An logged in admin go to the users page, where he sees a list of users(users_url)
2.And next to their name, the admin have an option of 'Set Mod', when the admin clicks that, it sets the user's mod attribute to true.
$ rails g migration add_mod_to_users mod:boolean
then in the migrated file, set default to false, and
$ rake db:migrate
In the routes.rb
get 'setmod' => 'users#setmod'
and in users_controller.rb
def setmod
if logged_in? && current_user.admin?
#user = User.find(params[:id])
#user.update_attribute(:mod, true)
flash[:success] = "User ID #{#user.id} is now a mod!"
redirect_to users_url
else
flash[:warning] = "You can't do that!"
redirect_to users_url
end
end
In the list of users view file:
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "Set Mod", setmod_path(:id => user.id) %>
Is this the right approach to it? I'm a beginner so i'd be glad if i can get some feedback on this. Does my code have any vulnerable spots? What's a better way doing it?
Also, i tried to do some test on this
in test/controllers/users_controller_test.rb
# This test passes
test "should redirect setmod when not logged in" do
get :setmod
assert_redirected_to users_url
end
# This test failed
test "should redirect setmod when logged in as a non-admin" do
log_in_as(#other_user)
assert_no_difference '#user.mod' do
get :setmod, id: #user
end
assert flash.empty?
assert_redirected_to users_url
end
I know the second test's code is wrong, but i can't figure out the right way to do it.
You're not passing a user id into the setmod route.
Change to this:
get 'setmod/:id' => 'users#setmod'
Now, navigating to http://localhost:3000/setmod/1 will attempt to set the user (with id=1) to mod.
However, you can use Rails' built-in resources to handle this just fine.
routes.rb
resources :users
your view
link_to("Promote the user to MOD", user_path(#user, status: 1), method: :put)
Clicking that link will update the single attribute for a user shown on the page. You would simply authenticate the admin in the users_controller.rb update action.

undefined local variable or method 'current_user'

I'm currently going through a RoR tutorial (http://railstutorial.org/chapters/sign-in-sign-out#sec:signin_success is the relevant section) which seems to be fairly good, although I've run into the following problem when trying to view the sample site.
Extracted source (around line #10):
7: <li><%= link_to "Home", root_path %></li>
8: <li><%= link_to "About", about_path %></li>
9:
10: <% if signed_in? %>
11: <li><%= link_to "Profile", current_user %></li>
12: <li><%= link_to "Sign out", signout_path, :method => delete %></li>
13: <% else %>
As you can see, the issue is stemming from my method "signed_in?" which is supposed to check if the user has logged in or not by checking whether the current_user variable is set (I've included the rest of the code from the helper to give a context, apologies):
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
current_user = user
end
def sign_out
cookies.delete[:remember_token]
current_user = nil
end
def current_user= (user)
#current_user ||= user_from_remember_token
end
def signed_in?
!current_user.nil?
end
private
def user_from_remember_token
User.authenticate_with_salt(*remember_token)
end
def remember_token
cookies.signed[:remember_token] || [nil, nil]
end
end
From my understanding, .nil? is a method that checks whether or not an object has been defined and therefore the object being undefined shouldn't generate an error but rather return false? I searched the tutorial for all cases of current_user (before checking to see if anyone else had this problem with little success) and my code seems correct so I'm a little confused and if anyone is able to help me understand the way Ruby variables are supposed to be accessed and why my code isn't working I'd be most grateful.
Edit:
I'm not sure if it's important with scope as I'm just beginning both Rails and Ruby, however the helper SessionsHelper is being used by my Users controller and views (it's included in my Applications controller)
I ran in to this same issue & it was for the same reason. You overlooked part of the instructions on 'Listing 9.16'.
def current_user= (user)
#current_user ||= user_from_remember_token
end
You were supposed to change this to the following.
def current_user
#current_user ||= user_from_remember_token
end
You'll also want to change all of the instances of *self.*current_user to *#*current_user.
Once you do this the error(s) are resolved.
Make sure you have the following code in the SessionHelper
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
The nil? method is not going to check whether a variable or method is defined. It is solely checking whether it is defined as a nil object or not. Ruby walks up the ancestors chain for SessionsHelper and finally determines that current_user is not defined anywhere (it will eventually end at Kernel#method_missing) and then throws an error. The quickest way to solve the problem would be:
#app/helpers/sessions_helper.rb
def current_user
#current_user ||= false
end
I asked a friend, and he corrected my errors. I think a large part of my mistake came from not being completely familiar with variable scope in Ruby and forgetting that everything is an object and therefore the method current_user=(user) was overriding the assignment function.
My friend's solution was to change the scope of current_user to an instanced variable (so it can be properly used), and change the function curent_user=(user) to a simple get_current_user function in order to determine if the current user exists in the cookie.
The final changed code is as follows:
#app/helpers/sessions_helper.rb
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
#current_user = user
end
def sign_out
cookies.delete(:remember_token)
#current_user = nil
end
def get_current_user
#current_user ||= user_from_remember_token
end
def signed_in?
!get_current_user.nil?
end
#app/views/layouts/_header.erb
<% if signed_in? %>
<li><%= link_to "Profile", get_current_user %></li>
<li><%= link_to "Sign out", signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "Sign in", signin_path %></li>
<% end %>
As you can see the variable in my header partial has also been changed to reflect the method used in the helper to obtain the user.
Going to start reading some basic Ruby guides so next time I get in over my head I have an idea of where to start fixing it :)

Preserving form submission through log in / sign up in Rails

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.

Resources