I use temporarily Rails as frontend app to communicate with an API.
After the authentication, I set the user_id in a cookie.
I use the her gem to call the User from the API and save it into an instance variable.
The issue is that I do this request on every page I and would like to do it once.
It's like #current_user is reset after each page.
def current_user
#User.find -> Her model
#current_user ||= User.find(cookies.signed[:user_id]) if cookies.signed[:user_id]
end
There no clear solution because your user coming from API. You can try to do something like that:
#remember user attributes without references
session['user'] = #current_user.attributes #remember
#user = OpenStruct(session['user']) #load, allow call #user.name etc, but not #user.posts
#use class variable
class User
include Her::Model
##tmp = {}
def remember
##tmp[id] = self
#call job etc to delete user from tmp to prevent something that reminds "memory leak"
end
def self.local_find(id)
##tmp[id]
end
end
def current_user
#current_user ||= User.local_find(cookies.signed[:user_id]) ||
User.find(cookies.signed[:user_id]) if cookies.signed[:user_id]
end
The main reason not to store(remember) objects in the session(long-term variable) is that if the object structure changes, you will get an exception.
Related
I have a question I am really new in Rails and I'm not sure what is correct.
def current_user
return unless params[:user_id]
#current_user ||= User.find(params[:user_id])
end
So like this I get the current user. Rails check the params for user_id.
But what if I something like that the route /users/:id
Then the params is id and not user_id this is why it fails sometimes with to set the time zone correctly.
How to handle something like this ?
def set_time_zone(&block)
if params[:user]
Time.use_zone(params[:user][:time_zone], &block)
else
time_zone = current_user.try(:time_zone) || 'UTC'
Time.use_zone(time_zone, &block)
end
end
you could override the current_user inside the UsersController so that it looks at the id param instead of user_id. or you can use the existing method with a fallback like so:
#current_user ||= User.find_by_id(params[:user_id]) || User.find_by_id(params[:id])
i'd caution you though not to take that params[:id] for granted. if my user id is 5 there's nothing to stop me from visiting /users/6!
In all the rails apps i've seen so far current_user represents the currently logged in user.
The id of the currently logged in user MUST NEVER be sent via params but via session (because params can be manipulated by everybody).
User submits username/password via form
controller verifies username/password matches and if correct
sets id of user to session
Then you can have
def current_user
#current_user ||= begin
User.find(session[:user_id]) if session[:user_id]
end
end
in your application controller.
I'm new to rails and building app from data on an api, I have two tables I want to use for users - students and educators.
I can authenticate in the controllers with
private
def fetch_user_data(username, password)
require 'URI'
uri = URI('the url for the api')
res = Net::HTTP.post_form(uri, 'username' => username, 'password' => password)
xml = res.body
doc = Nokogiri::Slop(xml)
#status = doc.auth.status.content.to_s
#username = doc.auth.username.content.to_s
#token = doc.auth.token.content.to_s
#person_id = doc.auth.person_pk.content.to_i
#security_roles = doc.auth.security_roles.content.to_s.downcase
end
def assign_user
if /faculty/ =~ #security_roles
#user = Educator.find_by(person_id: #person_id)
elsif /student/ =~ #security_roles
#user = Student.find_by(person_id: #person_id)
end
end
def authenticate_user(username, password)
fetch_user_data(username, password)
assign_user
session[:user_id] = #user.id
redirect_to #user
end
Now I know it's probably not pretty, but I'm learning as I go. I use the authenticate_user() in the sessions controller, and based on the redirect, the authentication seems fine. I do have one question about the :user_id key in the session -- is that a key only created for the session or is it trying to pull a value from a user table? Knowing that would help. My guess is it's just created for the session, but I have no idea.
Ok so now for my real problem. I'm trying to use cancan and I'm getting stuck at defining current user.
I figured I could have the #current_user instance point to the #user I assigned in assign_user. This doesn't seem to be working though. I tried a couple things, but I'm stuck. Maybe I can't even do that? Larger question? Do I have to have a user model to make cancan work? Can I use the two models Educators and Students and mask the user references in cancan on those?
I tried this, but it's not working -- any help?
def current_user
#current_user ||= #user
end
edit: Figured out a bit.
1. methods were in ApplicationHelper. Moved current_user() to ApplicationController . Changed syntax to conventional, but added conditionals.
def current_user
if Student.where(id:session[:user_id]).count == 0
#current_user ||= Educator.find(session[:user_id])
else
#current_user ||= Student.find(session[:user_id])
end
end
This seems to fix things, and allows me to use both tables as the user models.
There are several ways to define current_user (if you aren't using Devise), but this one here is pretty standard:
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id])
end
end
As for your question about the session, the way you have it set up, you're setting session[:user_id] equal to #user.id (which is always the same for each user).
minor aside consider using the gem CanCanCan which is as it sounds. CanCan not maintained and some one set up replacement CanCanCan. You won'thave to change any code.
I am fairly new to Rails, so apologies if it's not an 'instance variable' I am talking about!
I am using Devise for authentication, so can use things like current_user throughout the app. The app I am building has a User model, but also a Keyholder model (who is a sort of moderator for that user), and a Guest (who has read-only access to some things for that user).
What I want to know is - can I set it up so that I can use e.g. access_user when logged in as the keyholder to access the same object as current_user - and if so, where do I put the code in my app? It's quickly becoming very verbose and un-Rails-like having to repeat myself otherwise.
What I am trying to achieve is being able to use 'access_user' instead of current_user, so that regardless of whether it is the user, keyholder or guest logged in, it will use the user object.
For example:
def access_user
if user_signed_in?
access_user = current_user
end
if keyholder_signed_in?
access_user = current_keyholder.user
end
if guest_signed_in?
access_user = current_guest.user
end
end
Thanks!
Class level instance variables could also help you.
def access_user
if user_signed_in?
#access_user = current_user
end
if keyholder_signed_in?
#access_user = current_keyholder.user
end
if guest_signed_in?
#access_user = current_guest.user
end
end
You can just set this method in ApplicationController, and expose it to a helper method.
class ApplicationController
helper_method :access_user
def access_user
#blah blah
end
end
When method in ApplicationController, it's available to all controllers.
When you use helper_method, it is exposed as helper method to be used in View. More about helper_method: http://apidock.com/rails/ActionController/Helpers/ClassMethods/helper_method
I thought methods such as name and email were default in rails?
In my static pages view, in profile.html.erb I have:
<% if logged_in? %>
<% provide(:title, #user.name) %>
<% else %>
<% provide(:title, 'Profile')%>
<% end %>
I put in my static_page_controller
def profile
#user = User.find_by_remember_token(:remember_token)
end
When I go to the console User.find_by_remember_token("actualtoken").name returns me the appropriate users name, but :remember_token does not. How do I make :remember_token = the logged in users remember token?
In my sessions_helper I have
def log_in(user)
cookies.permanent[:remember_token] = user.remember_token
current_user = user
end
def logged_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
def log_out
current_user = nil
cookies.delete(:remember_token)
end
private
def user_from_remember_token
remember_token = cookies[:remember_token]
User.find_by_remember_token(remember_token) unless remember_token.nil?
end
end
copying it to my static_pages_helper didn't accomplish anything.
Quick things you should be aware of the rails framework and the ruby language:
A function defined in any of your helpers will be available to all helpers and views (so there is no reason to copy and paste the same functions through different helpers);
You're probably using an authentication gem and I guess it is the Devise gem. If this is right, then you should not be overriding their helpers unless you have a reason to do this;
User.anything will call the static function anything from the User class;
user = User.find_by_anything(the_thing) is a class static helper provided by ActiveModel that will query the database looking for a user that has *anything = the_thing*; this user or nil will be returned;
user.an_attribute will call a function that returns the user specified attribute (which is the same as the column name of this attribute by default);
user.try(:anything) will try to call the function anything from the user and return its value. If user is nil, the returned value will also be nil.
That said, I guess you just wanted to retrieve the current user remember token, which can be accomplished with the following:
user = current_user.try(:remember_token)
EDITED: The question is a bit messy, but I also think the following code will work with your controller:
def profile
#user = User.find_by_remember_token(params[:remember_token])
end
You must access the request's parameters through the params hash.
EDIT: completely replaces my first answer with one hopefully not as stupid :-)
(While there are several ways to implement and manage sessions in Rails, the default uses a cookie in the browser to reference a key stored in memory. Sessions are created by a request from a browser, so while it's certainly possible to use the console to get at an existing session, it's probably not what you want.)
So your method, user_from_remember_token will either return a user or nil. What I don't see in your code is where you're setting the remember_token on the User model. I'll assume it's there, but you may want to have code that tells the user to log in if you don't find them. A common pattern would be
def current_user
#current_user ||= user_from remember_token
unless #current_user
flash[:notice] = "Yo! Log in first."
redirect_to login_path and return
end
end
There's no problem calling a model finder from a separate controller. But why call User.find_by_remember_token(:remember_token) -- you don't have the remember_token yet (right?). Don't you just want to call the current_user method in your sessions helper?
If the method is not visible, you may want to include or require the session helper in your application_controller.rb
I'm using the facebooker gem which creates a variable called facebook_session in the controller scope (meaning when I can call facebook_session.user.name from the userscontroller section its okay). However when I'm rewriting the full_name function (located in my model) i can't access the facebook_session variable.
You'll have to pass the value into your model at some point, then store it if you need to access it regularly.
Models aren't allowed to pull data from controllers -- it would break things in console view, unit testing and in a few other situations.
The simplest answer is something like this:
class User
attr_accessor :facebook_name
before_create :update_full_name
def calculated_full_name
facebook_name || "not sure"
end
def update_full_name
full_name ||= calculated_full_name
end
end
class UsersController
def create
#user = User.new params[:user]
#user.facebook_name = facebook_session.user.name
#user.save
end
end