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.
Related
The Rails 'flash' message system is very useful, but I've always found the details of its implementation make its use awkward.
Specifically, the flash messages are cleared at the end of a response. This means that you have to work out whether they're going to be used in a direct page render or a redirect, then inform the flash system by using 'now' if it's not a redirect.
This seems overly complex and error-prone.
Occasionally I find myself building things that need to exhibit flash-like behaviour, and the process I use is slightly different:
class FlashlikeStore
attr_accessor :session
def initialize(session)
session[:flashlike] ||= []
self.session = session
end
def add(name, data)
self.store << { name: name, data: data }
end
def read
session.delete(:flashlike).to_json
end
def any?
store && store.any?
end
protected
def store
session[:flashlike]
end
end
With a little syntactic sugar in the application helper I can easily add my name-value pairs, and the act of reading it deletes the data. (In this case I'm actually reading this in via AJAJ, but that isn't important here.)
The upshot is that messages are only ever read once, with no need up-front to determine or guess when they're going to appear. They're read when they're needed, then they go away.
One could argue, I suppose, that a call to an object shouldn't both read data and change state, so the 'read' method could be split into a 'read' and a 'wipe' if you wanted to be purist that way. (Although you probably wouldn't be using Rails if you were.)
So the question is this: am I missing something? Is there a compelling reason for the way Rails flash messaging works, with its need for the 'now' method?
What would be the best and more efficient way in Rails if I want to use a hash of about 300-500 integers (but it will never be modified) and use it in more than one view in the application?
Should I save the data in the database?, create the hash in each action that is used? (this is what I do now, but the code looks ugly and inefficient), or is there another option?
Why don't you put it in a constant? You said it will never change, so it fits either configuration or constant.
Using the cache has the downside that it can be dropped out of cache, triggering a reload, which seems quite useless in this case.
The overhead of having it always in memory is none, 500 integers are 4KB or something like that at most, you are safe.
You can write the hash manually or load a YAML file (or whatever) if you prefer, your choice.
My suggestion is create a file app/models/whatever.rb and:
module Whatever
MY_HASH = {
1 => 241
}.freeze
end
This will be preloaded by rails on startup (in production) and kept in memory all the time.
You can access those valus in view with Whatever::MY_HASH[1], or you can write a wrapper method like
module Whatever
MY_HASH = {
1 => 241
}.freeze
def self.get(id)
MY_HASH.fetch(id)
end
end
And use that Whatever.get(1)
If the data will never be changed, why not just calculate the values before hand and write them directly into the view?
Another option would be to put the values into a singleton and cache them there.
require 'singleton'
class MyHashValues
include Singleton
def initialize
#
#results = calculation
end
def result_key_1
#results[:result_key_1]
end
def calculation
Hash.new
end
end
MyHashValues.instance.result_key_1
Cache it, it'll do exactly what you want and it's a standard Rails component. If you're not caching yet, check out the Rails docs on caching. If you use the memory store, your data will essentially be in RAM.
You will then be able to do this sort of thing
# The block contains the value to cache, if there's a miss
# Setting the value is done initially and after the cache
# expires or is cleared.
# put this in application controller and make it a helper method
def integer_hash
cache.fetch('integer_hash') { ... }
end
helper_method :integer_hash
I have a class method (placed in /app/lib/) which performs some heavy calculations and sub-http requests until a result is received.
The result isn't too dynamic, and requested by multiple users accessing a specific view in the app.
So, I want to schedule a periodic run of the method (using cron and Whenever gem), store the results somewhere in the server using JSON format and, by demand, read the results alone to the view.
How can this be achieved? what would be the correct way of doing that?
What I currently have:
def heavyMethod
response = {}
# some calculations, eventually building the response
File.open(File.expand_path('../../../tmp/cache/tests_queue.json', __FILE__), "w") do |f|
f.write(response.to_json)
end
end
and also a corresponding method to read this file.
I searched but couldn't find an example of achieving this using Rails cache convention (and not some private code that I wrote), on data which isn't related with ActiveRecord.
Thanks!
Your solution should work fine, but using Rails.cache should be cleaner and a bit faster. Rails guides provides enough information about Rails.cache and how to get it to work with memcached, let me summarize how I would use it in your case
Heavy method
def heavyMethod
response = {}
# some calculations, eventually building the response
Rails.cache.write("heavy_method_response", response)
end
Request
response = Rails.cache.fetch("heavy_method_response")
The only problem here is that when ur server starts for the first time, the cache will be empty. Also if/when memcache restarts.
One advantage is that somewhere on the flow, the data u pass in is marshalled into storage, and then unmartialled on the way out. Meaning u can pass in complex datastructures, and dont need to serialize to json manually.
Edit: memcached will clear your item if it runs out of memory. Will be very rare since its using a LRU (i think) algoritm to expire things, and I presume you will use this often.
To prevent this,
set expires_in larger than your cron period,
change your fetch code to call the heavy_method if ur fetch fails (like Rails.cache.fetch("heavy_method_response") {heavy_method}, and change heavy_method to just return the object.
Use something like redis which will not delete items.
Lets say that on top of my Rails app there is a bar with piece of text displayed - latest hot deal, scheduled downtime notfication, something like that. It's a single, on of a kind information that needs to be accessed on basically every request, and may be updated from time to time. What is the best way to achieve this?
What I'd like to do is some kind of permanent global variable (accessible from controllers).
It will be updated very rarely, so there's no problem if for some time after update there will be an inconsistency between workers.
On the other hand, it should be persistent in case of server fault (periodic backup is enough).
It will be accessed really often, so it should be as fast as possible - preferably stay in memory.
Also, it's only one of a kind, so I'd really prefer not to bloat the app with a dedicated database model.
Something like that is damn easy in Node.js for example, but I couldn't find a single way to achieve this in Rails. What shall I do?
EDIT
Thanks for the answers so far, but while they're inspiring, I think that I should stress out one key functionality that they're all missing. The variable should be editable inside the app and persistent. While it's possible to edit your variables, in case of server restart I'm back to the default - which is bad.
It really depends on what you are looking for. You could do something very simply by putting it in your application_controller.rb
class ApplicationController < ActionController::Base
def system_message
"Come buy our amazing .99 iphone chocolate bar apps, with 100% more gamification!"
end
end
That function (and string) is then accessible from any controller in your application. You could also specify something in the after_initialize block in your application.rb file.
config.after_initialize do
::MYTEXT = "MY SUPER AMAZING TEXT"
end
You could also create your own file under the initializers directory, which is preloaded in rails.
so siteAnnounce.rb
MYANNOUNCEMENT = "NOW LISTEN TO ME!"
You may also want to check out this Railscast video about site wide announcements
I would store it in the database and let caching take care of it.
I feel that global variables are fine, when appropriate, for code that needs to share that common value in many places but that is the code, not the the user view.
This is clearly true in this case as the OP has bolded 'editable by the app'. So I would have a view that lets the users enter it, it gets stored in a db table and then recalled as needed (as cached once used once).
Well I had faced a similar problem.
My problem was I needed a global variable in all the levels (MVC).
We went to use Memcache to store the variable.
May be you can go for a similar solution.
And as an added bonus you can change it throughout the program.
You could declare it as a constant in an initializer:
config/initialzers/foo.rb:
MYVARIABLE = 'some string'
Accessible from anywhere in your application as MYVARIABLE
Ok, so here's what I did. Instead of just putting the value to an initializer, I've made there a simple class that handles it. The variable itself is stored in a predefined file. Besides of reading the file upon the initialization, the class updates file when the value is changed, and also re-read the file periodically to maintain consistency across workers. I've also put there some basic JSON handling and backup functionality to make life easier.
For anyone interested, here's the important code:
class Pomegranate
def initialize
#delay = 30.minutes
#path = "db/pomegranate.json"
#valid = Time.now - 1
validate
end
def get(*p)
validate
p.inject(#data) {|object,key| object[key] if object}
end
def set(*p, q, v)
hash = p.inject(#data) {|object,key| object[key]||={}}
hash[q] = v
end
def save
#valid = Time.now + #delay
File.open(#path,"w") {|f| f.write(#data.to_json)}
end
private
def validate
if #valid < Time.now
#data = ActiveSupport::JSON.decode(File.read(#path)) rescue {}
#valid = Time.now + #delay
#valid = Time.now - 1 if #data.empty?
end
end
end
$pom = Pomegranate.new
Source:
Where to put Global variables in Rails 3
Try putting it in your applicaton.rb like this:
module MyAppName
class Application < Rails::Application
YOUR_GLOBAL_VAR = "test"
end
end
Then you can call it with the namespace in your controllers, views or whatever..
MyAppName::Application::YOUR_GLOBAL_VAR
Another alternative would be using something like settingslogic. With settingslogic, you just create a yml config file and a model (Settings.rb) that points to the config file. Then you can access these settings anywhere in your rails app with:
Settings.my_setting
I've started putting constants and variables like this in the configuration object, e.g.
TestApp::Application.config.foo = 'bar'
TestApp::Application.config.something = { :a => 1, :b => 2 }
I have a long database query on one of our dashboard systems that I would like to cache as results do not need to be accurate in realtime but can give a "close enough" value from the cache.
I would like to do this without the user ever having to wait. I was looking at using something like
Rails.cache.write('my_val', 'val', :expires_in => 60.minutes)
to store this value, but I don't believe it gives the exact functionality that I want. I would like to call with
Rails.fetch('my_val') { create a background task to update my_val; return expired my_val}
It seems that my_val is removed from the cache when it expired though. Is there any way to access this expired value or perhaps another built in mechanism that would enable this functionality?
Thanks.
Just do this:
Rails.cache.write('my_val', 'val')
never expire
Now run your background job:
SomeLongJob.process
In the SomeLongJob.process job do this:
def SomeLongJob.process
some_long_calculation = Blah.calc
Rails.cache.write('my_val', some_long_calculation)
end
Now read the data with
def get_value
val = Rails.cache.read('my_val', 'val')
end
The :race_condition_ttl option for Rails.cache.fetch is REALLY close to what you are looking for: http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch
But from what I can tell, the first request is still blocked (it's just subsequent ones that get the old value while it's updating). Not sure why they didn't go all the way with it. It would be better if the pattern #drhenner mentioned was abstracted into an option like that, but I haven't seen one yet.