Rails - Store API data throughout session - ruby-on-rails

I am rails fresher, In my rails application am fetching some large data from one site through API, each time I load page it takes time to fetch and display the data, is there any way to store this data throughout session?

Yes, for sure. You can just store it in memcached or redis. Community has an awesome gem for key value storages Moneta
You don't posted the code, so all of the below is just an assumption.
You could just create a new session_id (or use existing one). Then just create new element in store with it and use it when you will need to.
session_id = SecureRandom.hex
store = Moneta.new(:Memcached, server: 'localhost:11211')
store[session_id] = 'value'
store[session_id] = {a: 1, b: 2}
store[session_id] = MarshallableRubyObject.new
Store can be expirable or you can just delete it when you don't need it anymore.

Related

How to fetch redis data using id in rails app

I am trying to implement redis cache on a rails application. Till now I am able to cache the active record data using redis cache. I am able to fetch all the records at once using get method. But I am having difficult time figuring out how to fetch a single record using id since the the data produced by redis is in string data type.
Following is the data cached by redis:
"set" "bookstore:authors" "[{\"id\":1,\"name\":\"Stephenie Meyer\",\"created_at\":\"2018-05-03T10:58:20.326Z\",\"updated_at\":\"2018-05-03T10:58:20.326Z\"},{\"id\":2,\"name\":\"V.C. Andrews\",\"created_at\":\"2018-05-03T10:58:20.569Z\",\"updated_at\":\"2018-05-03T10:58:20.569Z\"}]
Now I am calling
authors = $redis.get('authors')
to display all the authors.
How can I fetch a single author using his id?
Helper method to fetch authors
def fetch_authors
authors = $redis.get('authors')
if authors.nil?
authors = Author.all.to_json
$redis.set("authors", authors).to_json
$redis.expire("authors", 5.hour.to_i)
end
JSON.load authors
end
For your use case, using a hash is probably better. You could use the following commands to achieve that:
HSET bookstore:authors 1 "<author json>"
HGET bookstore:authors 1 // Returns that author's json data
Or you can store each author on its own:
SET bookstore:authors:1 <author_json>
GET bookstore:authors:1 // Returns that author's json data

Can I use Rails.cache to store short-lived session data?

We are already using cookie based sessions, and switching off them to file store sessions in not an option. However, I need a way to store larger amounts of session data (up to 10MG or so) -- beyond the limit of cookie session and, even it weren't, round-tripping that much data on multiple requests would be slow.
I am currently attempting to solve this by using (abusing?) Rails.cache. The basic setup is like this:
I post to a route, which has the following code:
# calculate some results...
Rails.cache.write('search_results' + session.id), search_results)
redirect_to '/results'
Inside GET /results, I read the cached data and send it to the client:
#results = Rails.cache.read('search_results' + session.id)
This works fine. However, if I subsequently make a request to another route like GET /results2 that also calls Rails.cache.read('search_results' + session.id), it will return nil. Even if all calls happen within a 5-10s span.
So my questions are:
Why does this happen? What determines when Rails.cache clean itself?
Is there a way to make this work?
Is there a better approach altogether that doesn't involve using a DB or redis?
Answer to your questions:
The problem with file cache store is that it stores file locally. Thus, if you have multiple servers, cache can be written to one server while cache is read on another server which will return ‘nil’. The solution is to use cache store that can be shared among multiple servers.
Using redis-store may be a solution: https://github.com/redis-store/redis-rails

Ruby on Rails instance variable caching based on parameters

I am storing users miscellaneous data in user_data table and when I am retrieving that data with the association I defined and then I am actually caching it using Ruby instance Variable caching like this.
def user_data(user_id)
#user_data || = User.find(user_id).data
end
but instance variable #user_data will be allocated value only first time when it's nil and once it hold data for a user lets say for user_id equal to 1,and when I am passing user_id 2 in this method it's returning data for user_id 1 , because it will not allocate new value to it so my question is how can I cache it's value based on parameters of function.
Take a look at the Caching with Rails guide. Rails can cache data on multiple levels from full page caching to fragment caching, I strongly advise you to read all this page so you can make a perceived choice.
For the low-level caching you can do this:
#user_data = Rails.cache.fetch("user_#{user_id}", expires_in: 1.hour) do
User.find(user_id).data
end
By default Rails stores cache on disk, but you can setup for memcache, memory store, and etc.
You can use a hash for a key-based intance-variable-cache. I think that does what you want.
def user_data(user_id)
#user_data ||= {}
#user_data[user_id.to_i] || = User.find(user_id).data
end

Getting data out of devise

I'm migrating away from rails. I will be using the same domain, so I'll get the _session_id cookie that rails uses and I can bring over the old sessions table.I would like to use this to extract data (the user_id) from the old session. I can not tell how to do this outside of rails.
Within a controller there's current_user of course or session["warden.user.user.key"], but how can I take the id, decrypt the data in the table, and pull stuff out on my own (besides running the old rails application and creating a route on that that returns the info I need and hitting it from my new application)?
I'm not entirely sure this is the best way, but I was intrigued so went down the rabbit hole. This works for my 4.1.10 app where sessions are stored in the cookie. You'll want to look at action pack's EncryptedCookieJar class and active support's CachingKeyGenerator and MessageEncryptor classes for details.
Obviously you'll need to replace the two strings that start "THE VALUE…".
key_generator = ActiveSupport::KeyGenerator.new('THE VALUE OF SECRET_KEY_BASE FROM config/secrets.yml', iterations: 1000)
caching_key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
caching_key_generator.generate_key('encrypted cookie')
caching_key_generator.generate_key('signed encrypted cookie')
secret = caching_key_generator.generate_key('encrypted cookie')
sign_secret = caching_key_generator.generate_key('signed encrypted cookie')
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: ActionDispatch::Cookies::NullSerializer)
session_value = CGI::unescape('THE VALUE OF THE SESSION COOKIE')
serialized_result = encryptor.decrypt_and_verify(session_value)
result = Marshal.load(serialized_result)
The result, for me, is a hash that looks exactly the session hash in Rails.
If it doesn't work for you, you may be using a different serializer so need to replace Marshal.load with whatever you need. Just take a look at serialized_result and see.

Page-view-counter with Rails and Memcached

I'm trying to implement a page-views-counter with Rails and memcached. Every time I render a page, through rails I increase a memcached key (key.incr is atomic). My main worry is the possibility where this key gets expired or deleted from memcached before I update my DB record. Even if I visit all the keys with frequency greater than their expiration time, memcached might delete a key in the meantime because of full memory.
Any suggestions?
Thank you
Dimitris
I would go with redis as a memcached replacement. It's perfect for realtime stats. It gives you the speed and atomic increments that you want, plus it persists. Problem solved.
If you want that data to be persitent, you must not write it to memcache (which is a caching mecanism, and not a data persistance storage).
Basically, what I'd probably do would be like this :
When trying to get a counter for a page :
Check if it's stored in memcache
if yes, use it
if not, fetch it from the DB and store it to memcache
When trying to write a counter (i.e. counter += 1) :
Write the data to the database (update ... set counter = counter + 1 where... )
select the data back from the database ; wrapping both update and select in a transaction might help : isolation is something databases do well.
and immediatly write it to memcache, so it's up to date for the next "get" operation
But I would not use memcache for persistance :
I would never write to memcache any data that has not been written to the database
persistance is the job of the database ; not of a caching engine.

Resources