[logowanie] Zakres widoczności instance variable

G’day.
Korzystam z has_secure_password (Rails 3.1.1). Napisałem kilka metod pomocniczych w ApplicationHelper:

[code=application_helper.rb]module ApplicationHelper
def current_user=(user)
@current_user = user
end
def current_user
@current_user
end

def sign_in(user)
session[:user_id] = user.id
self.current_user = user
end

def sign_out
session.delete(:user_id)
self.current_user = nil
end

def logged_in?
!self.current_user.nil?
end

def logged_out?
!self.logged_in?
end
end[/code]

[code=sessions_controller.rb]class SessionsController < ApplicationController
def new
end

def create
if user = User.find_by_email(params[:email]).try(:authenticate, params[:password])
sign_in(user)
redirect_to root_url, :notice => t(‘activerecord.messages.session.created’)
else
flash.now.alert = t(‘activerecord.attributes.session.error’)
render :new
end
end

def delete
if logged_in? then
sign_out
flash.notice = t(‘activerecord.messages.session.destroyed’)
else
flash.notice = t(‘app.messages.bad_user’)
end
redirect_to root_path
end
end[/code]
Testuję to w taki sposób:

[code=sessions_controller_spec.rb]describe SessionsController do
render_views

describe “POST create” do

describe "przy niepoprawnych danych" do
  before(:each) do
    @attr = {
      :email => "example@domain.com",
      :password => "invalid"
    }
  end
  
  it "powinien przekierować na stronę logowania i pokazać błąd" do
    post :create, @attr
    flash.now[:alert].should == I18n.t('activerecord.attributes.session.error')
    response.should render_template(:new)
  end
end

describe "przy poprawnych danych" do
  before(:each) do
    @user = Factory(:user,
                    :email => "joe@example.com", 
                    :password => "abcd",
                    :password_confirmation => "abcd")
    @attr = {
      :email => @user.email,
      :password => @user.password
    }
  end
  
  it "powinien zalogować użytkownika" do
    post :create, @attr
    controller.logged_in?.should == true
  end
  
  it "powinien przekierować na stronę główną i wyświetlić wiadomość" do
    post :create, @attr
    flash[:notice].should == I18n.t('activerecord.messages.session.created')
    response.should redirect_to root_path
  end
end

end

describe “GET new” do
it “powinno renderować new.html.haml” do
get :new
response.should render_template :new
end
end

describe “DELETE destroy” do
describe “jako niezalogowany” do
it “powinien przekierować na stronę główną z wiadomością” do
controller.should_not be_logged_in
delete :delete
flash[:notice].should == I18n.t(‘app.messages.bad_user’)
response.should redirect_to root_path
controller.should_not be_logged_in
end
end

describe "jako zalogowany" do
  it "powinien przekierować na stronę główną z wiadomością" do
    valid_user = Factory(:user)
    test_login(valid_user)
    controller.logged_out?.should == false
    delete :delete
    controller.logged_out?.should == true
    flash[:notice].should == I18n.t('activerecord.messages.session.destroyed')
    response.should redirect_to root_path
    
    controller.should_not be_logged_in
  end
end

end
end[/code]
I jeszcze w spec_helper.rb mam:

def test_login(user) controller.sign_in(user) end
W głównym layoucie w stopce mam informację o tym, czy użytkownik jest zalogowany i jakie jest jego id jeśli jest zalogowany. Pierwszą informację dostaję sprawdzając status logged_in?.
Teraz rzecz najdziwniejsza, wszystkie testy przechodzą bez szemrania, natomiast w przeglądarce po zalogowaniu status w stopce się nie zmienia, czyli jak było “nie” tak dalej jest “nie”, natomiast obok wyświetla się id użytkownika 1, i faktycznie to się zgadza. Czyli wnioskuję z tego, że jakoś źle ustawiam tę zmienną @current_user, tak, że nie jest ona widoczna przez cały czas w całej aplikacji. Prosiłbym o wskazanie źródła problemu.

Sprawdzanie czy user jest zalogowany przez istnienie zmiennej to moim zdaniem słaby pomysł :wink: Lepiej chyba sprawdzić czy istnieje dana wartość w sesji. Wtedy też możesz zmienić #current_user żeby w razie czego zwracała ci coś w stylu “Guest”

[code=ruby]module ApplicationHelper
def current_user
@current_user.presence || “Guest”
end

def logged_in?
session[:user_id].present?
end
end[/code]

Skąd bezstanowa (HTTP!) przecież aplikacja ma wiedzieć jaki ma być current_user? Zwłaszcza po nowym (od czasu zalogowania) żądaniu, np. redirect?

Nigdzie nie masz kodu który po zalogowaniu wkłada current_usera (jego id) do sesji, oraz wywołania wyciągnięcia current_usera na postawie owej danej z sesji. I dokładnie tego Ci brakuje.

Przecie ma:

[code=ruby]class SessionsController < ApplicationController
def create
if user = User.find_by_email(params[:email]).try(:authenticate, params[:password])
# !! O, tutaj! :smiley:
sign_in(user)
redirect_to root_url, :notice => t(‘activerecord.messages.session.created’)
else
flash.now.alert = t(‘activerecord.attributes.session.error’)
render :new
end
end
end

module ApplicationHelper
def sign_in(user)
session[:user_id] = user.id
self.current_user = user
end
end[/code]
Ale Tomash ma rację, w #current_user powinno być coś takiego:

def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end

Kurde, przeoczyłem. Pewnie dlatego że w helperze?

moze zrob cos w stylu

def current_user @current_user ||= User.find(session[:user_id]) end

możesz też rzucić okiem na moją prezentację o Authologic i Rails 3

Kurde, przeoczyłem. Pewnie dlatego że w helperze?[/quote]
Aaa true, true :wink:

@bfo możesz w takim razie zrobić tak:

[code=ruby]class ApplicationController < ActionController::Base
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id].present?
end

helper_method :current_user
end[/code]
itd. itd.

Genialnie! Dzięki za rozwiązanie i materiały.