Rails: How to find out logged users in my rails application? - ruby-on-rails

I save user ids to session[:user_id] from database when user logs in . What should I do to find out number of logged in users?
I use ActionDispatch::Session::CacheStore for session storage.
Please guide me the correct way to achieve my objective.

Probably the best solution would be to switch to using the Active Record Session store. It's easy to do - you just need to add (and run) the migration to create the sessions table. Just run:
rake db:sessions:create
Then add the session store config to: config/initializers/session_store.rb like so:
YourApplication::Application.config.session_store :active_record_store
Once you've done that and restarted your server Rails will now be storing your sessions in the database.
This way you'll be able to get the current number of logged in users using something like:
ActiveRecord::SessionStore::Session.count
Although it would be more accurate to only count those updated recently - say the last 5 minutes:
ActiveRecord::SessionStore::Session.where("updated_at > ?", 5.minutes.ago).count
Depending on how often you need to query this value you might want to consider caching the value or incrementing/decrementing a cached value in an after create or after destroy callback but that seems like overkill.

When a session is created or destroyed, you could try implementing a session variable that increments or decrements and use some helpers to increment/decrement the counter.
def sessions_increment
if session[:count].nil?
session[:count] = 0
end
session[:count] += 1
end
def sessions_decrement
if session[:count].nil?
session[:count] = 0
end
session[:count] -= 1
end

Related

Rails query caching without refresh on element update

I put many question about caching in here but no one answer. So mayby i will have answer on this.
I have in my search model code like this:
users = users.where('id >?', my_value)
Rails.cache.fetch('my_cached',expires_in: 3.minutes) do
users = users.order('current_sign_in_at DESC')
end
Can somebody explain me what this do. I thought that this will return sorted users table and put it in cache for 3 minutes so when i require next search it will return me my_cached result if it doesnt expired.
But it does not work like that. When some user login and current_sign_in_at is changed - cache is override and new query is returned.
I am not very experienced with the caching procedure for RoR, but I think what you say is right.
I thought that this will return sorted users table and put it in cache
for 3 minutes so when i require next search it will return me
my_cached result if it doesnt expired.
Then, I think that the caching should be done in this matter:
Rails.cache.fetch('my_cached',expires_in: 3.minutes) do
User.where('id >?', my_value).order('current_sign_in_at DESC')
end
Remember, that can exist some rules that can expire the cache before the expiration time assigned. This depends how the User model is configured.
Again, the query will not be executed only if you use the variable my_cached. Maybe the query you see is coming from another procedure (maybe from Devise itself if you use it?)
SOME HELPFUL REFERENCE
https://devcenter.heroku.com/articles/caching-strategies#low-level-caching
http://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store
http://robotmay.com/post/23161612605/everyone-should-be-using-low-level-caching
UPDATE
If the variable my_value changes frequently (can be an aleatory value), then the cache will be used only if the my_value is the same as a previous (in 3 minutes) value.
Maybe an alternative solution for a variable my_value can be:
Rails.cache.fetch('my_cached',expires_in: 3.minutes) do
User.all # cache all users
end
# then filter the cached value by the my_value and order it

Race condition with rails sessions

There is an array inside the session hash which I'm adding things to it. The problem is, sometimes multiple requests get processed at the same time (because ajax), and the changes a request makes to the array is then replaced by the changes made by the second request.
Example, the array first looks like this:
[63, 73, 92]
Then the first request adds something to it:
[63, 73, 92, 84]
The second request does the same thing (but works on the older version obviously):
[63, 73, 92, 102]
So in the end the array doesn't look like it should. Is there a way to avoid that?
I tried to use the cache store, the active record store and the cookie store. Same problem with all of them.
Thanks.
Session race conditions are very common in Rails. Redis session store doesn't help either!
The reason is Rails only reads and creates the session object when it receives the request and writes it back to session store when request is complete and is about to be returned to user.
Java has a solution for this called session replication. We can build our own concurrent redis based session store. The following is pretty much it. Except the update method is missing the lock. But it almost never happens to get race condition in it.
To get session hash just use concurrent_session which returns a hash.
To set some value in it, use update_concurrent_session method. It deep merges the new hash into the old value:
class ApplicationController < ActionController::Base
def concurrent_session
#concurrent_session ||= get_concurrent_session
end
def update_concurrent_session h
return unless session.id.present?
#concurrent_session = get_concurrent_session.deep_merge(h)
redis.set session_key, #concurrent_session.to_json
redis.expire session_key, session_timeout
end
private
def redis
Rails.cache.redis
end
def session_timeout
2.weeks.to_i
end
def session_key
'SESSION_' + session.id.to_s if session.id.present?
end
def get_concurrent_session
return {} unless session.id.present?
redis.expire session_key, session_timeout
redis.get(session_key).yield_self do |v|
if v
JSON.parse v, symbolize_names: true
else
{}
end
end
end
end
Usage example:
my_roles = concurrent_session[:my_roles]
update_concurrent_session({my_roles: ['admin', 'vip']})
There really isn't a great solution for this in Rails. My first suggestion would be to examine your use case and see if you can avoid this situation to begin with. If it's safe to do so, session data if often best kept on the client, as there are a number of challenges that can come up when dealing with server-side session stores. On the other hand, if this is data that might be useful long term across multiple page requests (and maybe multiple sessions), perhaps it should go in the database.
Of course, there is some data that really does belong in the session (a good example of this is the currently logged-in user). If this is the case, have a look at http://paulbutcher.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/, and specifically https://github.com/fcheung/smart_session_store, which tries to deal with the situation you've described.
It is a simple race condition, just lock the request using any locking mechanism like
redis locker
RedisLocker.new('my_ajax').run! { session[:whatever] << number }
Load the current session, or create a new one if necessary
Save a copy of the unmodified session for future reference
Run the code of the action
Compare the modified session with the copy saved previously to determine what has changed
If the session has changed:
Lock the session
Reload the session
Apply the changes made to this session and save it
Unlock the session
From Race conditions in Rails sessions and how to fix them.

Rails: Moving from Active Record Session Store to a Redis Store

I have a large application that as many, many thousand active sessions. I want to migrate into a Redis session store using this. And ideally, I want my current sessions to stay active.
Does anyone have any experience in migrating active sessions. I assume I write either a migration or a rake task (I think migration, so I can drop the old table as part of this), and I want to just write into redis all the current details.
old_sessions = ActiveRecord::Base.connection.select_all("select * from sessions")
old_sessions.each { |session| $redis.set(????? ????) }
But I am worried about data integrity.
Alright, after a day of hacking this up, here's what I came up with:
class MoveActiveRecordSesionsIntoRedis < ActiveRecord::Migration
def up
#get all the sessions from the last month
old_sessions = ActiveRecord::Base.connection.select_all("select * from sessions where updated_at > '#{Time.now - 1.month}'")
old_sessions.each do |session|
#convert the base64 data back into the object
data = ActiveRecord::SessionStore::Session.unmarshal(session["data"])
#load each session into Redis, dumping the object appropriately
$redis.setex session["session_id"],
1.month.to_i,
Marshal.dump(data).to_s.force_encoding(Encoding::BINARY)
end
#drop the old session table (So long unecessary 3Gigs!)
drop_table :sessions
end
def down
raise ActiveRecord::IrreversibleMigration, "Session face-plant!"
end
end
I'm putting this here as a reference. Or if you see something wrong with it, I'm all ears.

Rails Devise: How do I (mem)cache devise's database requests for the user object?

Every time I hit an authenticated page, I notice devise issuing an SQL statement :
User Load (0.2ms) SELECT users.* FROM users WHERE (users.id = 1) LIMIT 1
(I'm using Rails 3 btw .. so cache_money seems out as a solution and despite a lot of searching I've found no substitute).
I tried many overrides in the user model and only find_by_sql seems called. Which gets passed a string of the entire SQL statement. Something intuitive like find_by_id or find doesn't seem to get called. I 'can' override this method and glean the user-id and do a reasonable cache system from that - but that's quite ugly.
I also tried overriding authenticate_user which I can intercept one SQL attempt but then calls to current_user seems to try it again.
Simply, my user objects change rarely and its a sad state to keep hitting the db for this instead of a memcache solution. (assume that I'm willing to accept all responsibility for invalidating said cache with :after_save as part but not all of that solution)
The following code will cache the user by its id and
invalidate the cache after each modification.
class User < ActiveRecord::Base
after_save :invalidate_cache
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
user = Rails.cache.fetch("user:#{single_key}") do
User.where(:id => single_key).entries.first
end
# validate user against stored salt in the session
return user if user && user.authenticatable_salt == salt
# fallback to devise default method if user is blank or invalid
super
end
private
def invalidate_cache
Rails.cache.delete("user:#{id}")
end
end
WARNING: There's most likely a better/smarter way to do this.
I chased this problem down a few months back. I found -- or at least, I think I found -- where Devise loads the user object here:
https://github.com/plataformatec/devise/blob/master/lib/devise/rails/warden_compat.rb#L31
I created a monkey patch for that deserialized method in /initializers/warden.rb to do a cache fetch instead of get. It felt dirty and wrong, but it worked.
I've been struggling with this, too.
A less convoluted way of doing this is to add this class method to your User model:
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
Rails.cache.fetch("user:#{single_key}") { User.find(single_key) }
end
Note that I'm prepending the model name to the object ID that is passed in for storing/retrieving the object from the cache; you can use whatever scheme fits your needs.
The only thing to worry about, of course, is invalidating the user in the cache when something changes. It would have been nice instead to store the User in the cache using the session ID as part of the key, but the session is not available in the model class, and is not passed in to this method by Devise.

How do I change a variable every so minutes in rails?

I want to display a random record from the database for a certain amount of time, after that time it gets refreshed to another random record.
How would I go about that in rails?
Right now I'm looking in the directions of cronjobs, also the whenever gem, .. but I'm not 100% sure I really need all that for what seems to be a pretty simple action?
Use the Rails.cache mechanism.
In your controller:
#record = Rails.cache("cached_record", :expires_in => 5.minutes) do
Model.first( :offset =>rand(Model.count))
end
During the first execution, result gets cached in the Rails cache. A new random record is retrieved after 5 minutes.
I would have an expiry_date in my model and then present the user with a javascript timer. After the time has elapsed, i would send a request back to the server(ajax probably, or maybe refreshing the page) and check whether the time has indeed expired. If so, i would present the new record.
You could simply check the current time in your controller, something like:
def show
#last_refresh ||= DateTime.now
#current ||= MyModel.get_random
#current = MyModel.get_random if (DateTime.now - #last_refresh) > 5.minutes
end
This kind of code wouldn't scale to more servers (as it relies on class variables for data storage), so in reality you would wan't to store the two class variables in something like Redis (or Memcache even) - that is for high performance. Depends really on how accurately you need this and how much performance you need. You could as well use your normal database to store expiry times and then load the record whose time is current.
My first though was to cache the record in a global, but you could end up with different records being served by different servers. How about adding a :chosen_at datetime column to your record...
class Model < AR::Base
def self.random
##random = first(:conditions => 'chosen_at NOT NULL')
return ##random unless ##random.nil? or ##random.chosen_at < 5.minutes.ago
##random.update_attribute(:chosen_at,nil) if ##random
ids = connection.select_all("SELECT id FROM things")
##random = find(ids[rand(ids.length)]["id"].to_i)
end
end

Resources