How should I add a custom property to a rails helper method? - ruby-on-rails

I have a Rails helper method...
def current_user
#user = User.find(cookies[:user_id]) if cookies[:user_id]
end
And I want to add a non-database property to it...
self.custom_property = true
So I can access it in my view.
<%= current_user.custom_property %>
But, Rails says "Undefined method: custom_property"
Here's the full code:
def current_user
#user = User.find(cookies[:user_id]) if cookies[:user_id]
#user.ribbon_array ||= []
self.is_moderator = #user.ribbon_array.include?(1) ? true : false
end
I'd like to do it like this so in my view I can check current_user.is_moderator. Elsewhere in my code I have an #is_moderator variable that's specific to each page, but this one would be used across the whole app and specific to the current_user as opposed to the user on the profile.

To accomplish this
I'd like to do it like this so in my view I can check
current_user.is_moderator. Elsewhere in my code I have an
#is_moderator variable that's specific to each page, but this one
would be used across the whole app and specific to the current_user as
opposed to the user on the profile.
I recommend you to implement a method on your user model called
def is_moderator_of(page)
# returns true or false here
end
Or better yet, use a authorization gem like cancan
EDIT:
Also, this
#user = User.find(cookies[:user_id]) if cookies[:user_id]
#user.ribbon_array ||= []
will generate "Method not found: ribbon_array for nil" if there is no cookies[:user_id]

Edit
My bad without too much thought. In this case maybe a better solution is to add another helper method. Helpers themselves are not so object oriented, so you should be able to tolerate this kind of solution as well.
def current_user
#current_user = User.find(cookies[:user_id]) if cookies[:user_id]
end
def is_moderator?
ribbon = #current_user.ribbon_array || []
ribbon.include?(1)
end

In your app/models/user.rb file:
class User << ActiveRecord::Base
attr_accessor :custom_property
...
end
Then your view code should work.
Alternatively, you could just set #custom_property = true in the helper, and use #custom_property in the view.

Related

Rails 5 set current_user from external API

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.

Can I use CanCan with model(s) other than User?

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.

Rails - calling an instance variable from anywhere in the app

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

Why am I getting error Undefined method `name' for nil:NilClass with Ruby on Rails?

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

Rails 3 devise, current_user is not accessible in a Model ?

in my project.rb model, I'm trying to create a scope with a dynamic variable:
scope :instanceprojects, lambda {
where("projects.instance_id = ?", current_user.instance_id)
}
I get the following error:
undefined local variable or method `current_user' for #<Class:0x102fe3af0>
Where in the controller I can access current_user.instance_id... Is there a reason the model can't access it and a way to get access? Also, is this the right place to create a scope like the above, or does that belong in the controller?
This doesn't make much sense, as you already pointed. The current_user doesn't belong to model logic at all, it should be handled on the controller level.
But you can still create scope like that, just pass the parameter to it from the controller:
scope :instanceprojects, lambda { |user|
where("projects.instance_id = ?", user.instance_id)
}
Now you can call it in the controller:
Model.instanceprojects(current_user)
The already accepted answer provides a really correct way to achieve this.
But here's the thread-safe version of User.current_user trick.
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
This works as expected, however it can be considered dirty, because we basically define a global variable here.
Ryan Bates lays out a pretty safe way to implement this kind of strategy in this railscast
You can browse the source code here
Here he creates a current_tenant method, but you could easily substitute current_user instead.
Here are the key bits of code...
#application_controller.rb
around_filter :scope_current_tenant
private
def current_tenant
Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant
def scope_current_tenant
Tenant.current_id = current_tenant.id
yield
ensure
Tenant.current_id = nil
end
#models/tenant.rb
def self.current_id=(id)
Thread.current[:tenant_id] = id
end
def self.current_id
Thread.current[:tenant_id]
end
Then in the model you can do something like...
default_scope { where(tenant_id: Tenant.current_id) }
You don't need to use scopes. If you have set the appropriate associations in models, following piece of code placed in controller should do the trick:
#projects = current_user.instance.projects

Resources