how to proper cache a variable in rails - ruby-on-rails

Hi I have a rails App which displays always the company name on each page.
Since a logged in user can have multiple companies she belongs to.
User and companies are stored in the db.
I use authlogic for the user management.
Now I do not want to hit the database on every postback or page change
What would be best practise to chache/store the company until the logged in users changes or the user selects a different company? Something like global instance vars for a given user.
I started with this in my application_controller
def current_company
return #current_company if defined?(#current_company)
#current_company = Account.includes(:users).where(:users =>current_company)
end
and I realized that I am still hitting the db...
Is the session the recommended way or what would be best practice for this...
Thanks for the help in advance

||= way:
def current_company
#current_company ||= Account.includes(:users).where(:users =>current_company)
end
memoize way:
def current_company
Account.includes(:users).where(:users =>current_company)
end
memoize :current_company
Differences between this method and normal memoization with ||=
http://apidock.com/rails/ActiveSupport/memoize#447-Differences-between-normal-or-assign-operator
#tadman, you are right but from my point of view depends how complex its the method that you are trying to "cache". For simple cases I prefer ||=

I think this is what you're looking for
https://github.com/nkallen/cache-money

Related

Using current user in Rails in a model method

I'm currently trying to implement simple audit for users (just for destroy method). This way I know if the user has been deleted by an admin or user deleted itself. I wanted to add deleted_by_id column to my model.
I was thinking to use before_destroy, and to retrieve the user info like described in this post :
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
module UserInfo
def current_user
Thread.current[:user]
end
def self.current_user=(user)
Thread.current[:user] = user
end
end
But this article is from 2007, I'm not sure will this work in multithreaded and is there something more up to date on this topic, has anyone done something like this lately to pass on the experience?
Using that technique would certainly work, but will violate the principle that wants the Model unaware of the controller state.
If you need to know who is responsible for a deletion, the correct approach is to pass such information as parameter.
Instead of using callbacks and threads (both represents unnecessary complexity in this case) simply define a new method in your model
class User
def delete_user(actor)
self.deleted_by_id = actor.id
# do what you need to do with the record
# such as .destroy or whatever
end
end
Then in your controller simply call
#user.delete_user(current_user)
This approach:
respects the MVC pattern
can be easily tested in isolation with minimal dependencies (it's a model method)
expose a custom API instead of coupling your app to ActiveRecord API
You can use paranoia gem to make soft deletes. And then I suggest destroying users through some kind of service. Check, really basic example below:
class UserDestroyService
def initialize(user, destroyer)
#user = user
#destroyer = destroyer
end
def perform
#user.deleted_by_id = #destroyer.id
#user.destroy
end
end
UserDestroyService.new(user, current_user).perform

Preferred way to use sessions to avoid hitting the database in rails

I try to optimise a Rails app with big load that currently hit the databsae on every request. I try now to optimise this by saving some info on the session so I don't need to go the database every time. I'm currently doing something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
else
session[:audience] ||= 'general'
end
end
And then calling session[:audience] anywhere on my controller and views. Seems fine except that I'm seeing on the New Relic logs that sometimes the session is not set and therefore the app get a nasty nil.
Im thinking better I should use instance variables, maybe more something like this.
def set_audience
if current_user
session[:audience] ||= current_user.audience
end
#audience = session[:audience]
#audience = 'general' if #audience.empty?
end
And then calling #audience in my app.
Is this correct? I would like to make sure I'm used the preferred approach to this.
I think the standard approach here would be to use a helper method on ApplicationContoller:
class ApplicationController < ActionController::Base
private
def current_audience
#current_audience ||= current_user.audience
end
helper_method :current_audience
end
This will work pretty much exactly like the current_user helper method in your controllers and views. Depending on the specifics of your application, you may want to add some more robust nil handling, but this is the basic idea.

Setting current_user in pg_audit_log

I'd like to use pg_audit_log for logging in a rails app. The audit log must not only show the columns that have changed, but also the user who made those changes. The docs don't show how to do this, but after looking through the pg_audit_log source (postgresql_adapter.rb) I see it reads user information from a thread local variable, ala:
current_user = Thread.current[:current_user]
I've considered setting/unsetting this in before and after filters like so:
Thread.current[:current_user] = current_user
(using the current_user helper method in the controller to get the currently logged in user), but that seems dangerous. I'm now spending time trying to understand how the rails request cycle and threads interact, to get a better feel for just how dangerous. In the mean time, I was curious if any SO users currently using pg_audit_log have solved the need to log the user_id and user_unique_name to the log tables each time the user makes a change to a record.
Setting the current user the way you describe is a common way to do it. See, for example, http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
Some example code could look like:
# in your model
class User < ActiveRecord::Base
def self.current
Thread.current[:current_user]
end
def self.current=(user)
Thread.current[:current_user] = user if user.nil? || user.is_a?(User)
end
end
# in your controller
class ApplicationController < ActionController::Base
before_filter :set_current_user
def set_current_user
User.current = user_signed_in? ? current_user : nil
end
end
Relying on the Thread.current hash to provide model-level access to objects managed by the controller is indeed controversial. For example, see the following:
Safety of Thread.current[] usage in rails
It is worrisome that this particular feature is undocumented in the pg_audit_log gem.
Suppose you had not actively explored the gem's source code, and suppose you had independently decided to define Thread.current[:current_user] = something in your own application, for your own purpose. In that case, pg_audit_log would audit that object, without your knowledge.
Granted, the name current_user is so universally accepted to mean the currently logged-on user as defined by authentication routines that it's difficult to imagine this potential bug as a concrete problem, but from a design standpoint? Ouch.
On the other hand, since you know what you are doing, ensuring that Thread.current[:current_user] is set/unset at the beginning/end of each and every response cycle should make the process safe. At least that's what I gather from reading lots of posts on the topic.
Cheers, Giuseppe

Rails scope model data by user_id

I have a Rails application where a user longs in and I have the user_id in the session. The next step is to create a scope for all model data shown to the user where data.user_id = session[:user_id].
I know I can do the following in each of my controllers
Controller.find_all_by_user_id(session[:user_id])
Yet to me it seems there is probably a better solution. I found the possibility to add a scope to the model, yet the session is not known here and MVC pattern wise it is probably not a good idea to have it there. Is there a solution to apply such a user_id restriction to all data coming from the models or should I just use the find_all_by_user_id for every controller function that has userdata in it?
If I understand you correctly you want to access some data by user_id. Which means that you can define relationship in the user model as has_many :this_and_that or something like that. It it is right, then you can create a before_filter or even better a function in your application controller in which you get your current user instance. Trough this instance, you can access all available data to that user. You can even make that function a helper function, and you can use that in a view.
#User.erb
has_many :other_data
#ApplicationConroller.erb
def current_user
#current_user ||= User.find_by_id(session[:user_id])
end
#OtherControllers
def index
#other_datas = current_user.other_datas
end
You can have before_filter :load_user_data, :if => current_user or something like that.
Method will look like that:
def load_user_data
#data = ModelName.where(user_id: session[:user_id])
end
#data will be ActiveRecord::Relation and will be chainable. Also using find_all_by_user_id in each controller not that bad, if you really need it.

Setting a class variable for finding current user

I need to find the current logged in user in my model.
I defined cattr_accessor current_logged_in in User model.
Now, when a user logs in I set User.current_logged_in = current_user.id.
Later, in other models I access the variable using User.current_logged_in_user. As of now it works.
Is it the right way to implement this?
A good way to implement it, unless you're just doing your code as a learning exercise is to use a plugin like devise.
That said, you should avoid accessing the current_user in models. current_user is a session thing and should not be tied to the model. Instead pass in the current_user as a parameter to methods in the model. Something like:
def can_delete_item(user)
if user.is_admin?
....
else
.....
end
.....
end

Resources