Access current_user in model

2019-01-02 15:00发布

问题:

I have 3 tables

items (columns are:  name , type)
history(columns are: date, username, item_id)
user(username, password)

When a user say "ABC" logs in and creates a new item, a history record gets created with the following after_create filter. How to assign this username ‘ABC’ to the username field in history table through this filter.

class Item < ActiveRecord::Base
  has_many :histories
  after_create :update_history
  def update_history
    histories.create(:date=>Time.now, username=> ?) 
  end
end

My login method in session_controller

def login
  if request.post?
    user=User.authenticate(params[:username])
    if user
      session[:user_id] =user.id
      redirect_to( :action=>'home')
      flash[:message] = "Successfully logged in "
    else
      flash[:notice] = "Incorrect user/password combination"
      redirect_to(:action=>"login")
    end
  end
end

I am not using any authentication plugin. I would appreciate if someone could tell me how to achieve this without using plugin(like userstamp etc.) if possible.

回答1:

Rails 5

Declare a module

module Current
  thread_mattr_accessor :user
end

Assign the current user

class ApplicationController < ActionController::Base
  around_action :set_current_user
  def set_current_user
    Current.user = current_user
    yield
  ensure
    # to address the thread variable leak issues in Puma/Thin webserver
    Current.user = nil
  end             
end

Now you can refer to the current user as Current.user

Documentation about thread_mattr_accessor

Rails 3,4

It is not a common practice to access the current_user within a model. That being said, here is a solution:

class User < ActiveRecord::Base
  def self.current
    Thread.current[:current_user]
  end

  def self.current=(usr)
    Thread.current[:current_user] = usr
  end
end

Set the current_user attribute in a around_filter of ApplicationController.

class ApplicationController < ActionController::Base
  around_filter :set_current_user

  def set_current_user
    User.current = User.find_by_id(session[:user_id])
    yield
  ensure
    # to address the thread variable leak issues in Puma/Thin webserver
    User.current = nil
  end             
end

Set the current_user after successful authentication:

def login
  if User.current=User.authenticate(params[:username], params[:password])
    session[:user_id] = User.current.id
    flash[:message] = "Successfully logged in "
    redirect_to( :action=>'home')
  else
    flash[:notice] = "Incorrect user/password combination"
    redirect_to(:action=>"login")
  end
end

Finally, refer to the current_user in update_history of Item.

class Item < ActiveRecord::Base
  has_many :histories
  after_create :update_history
  def update_history
    histories.create(:date=>Time.now, :username=> User.current.username) 
  end
end


回答2:

The Controller should tell the model instance

Working with the database is the model's job. Handling web requests, including knowing the user for the current request, is the controller's job.

Therefore, if a model instance needs to know the current user, a controller should tell it.

def create
  @item = Item.new
  @item.current_user = current_user # or whatever your controller method is
  ...
end

This assumes that Item has an attr_accessor for current_user.



回答3:

If the user creates an item, shouldn't the item have a belongs_to :user clause? This would allow you in your after_update to do

History.create :username => self.user.username


回答4:

You could write an around_filter in ApplicationController

around_filter :apply_scope 

def apply_scope 

  Document.where(:user_id => current_user.id).scoping do 
  yield 

end 


回答5:

This can be done easily in few steps by implementing Thread.

Step 1:

class User < ApplicationRecord

  def self.current
    Thread.current[:user]
  end

  def self.current=(user)
    Thread.current[:user] = user
  end

end

Step 2:

class ApplicationController < ActionController::Base
  before_filter :set_current_user

  def set_current_user
    User.current = current_user
  end
end

Now you can easily get current user as User.current



回答6:

The Rails 5.2 approach for having global access to the user and other attributes is CurrentAttributes.



回答7:

The Thread trick isn't threadsafe, ironically.

My solution was to walk the stack backwards looking for a frame that responds to current_user. If none is found it returns nil. Example:

def find_current_user
  (1..Kernel.caller.length).each do |n|
    RubyVM::DebugInspector.open do |i|
      current_user = eval "current_user rescue nil", i.frame_binding(n)
      return current_user unless current_user.nil?
    end
  end
  return nil
end

It could be made more robust by confirming the expected return type, and possibly by confirming owner of the frame is a type of controller...



标签: