Too many sessions in a rails application - ruby-on-rails

I am building an E-commerce website on ruby on rails from scratch.(This is my first project on ruby on rails)
My product belongs to a subcategory which in-turn belongs to a category.
My filters partial include multiple check-boxes for category,subcategory,additional_category(Like hand made clothes,factory built etc.),lifestyle(relaxed,corporate etc) and cloth_material_type(this has around 30 options)
I am sending 5 arrays for each of these cases to the backend to search through the associations.
Now when a non logged in user reloads the page the filters set by user resets to default.
To avoid this I have four options in mind.
Option 1. Store the filter values set by the user in the cookies which is fast.But it might slow down the user's browser.
Option2 . Store the values in a session using ActiveRecord::SessionStore gem which will increase the size of session for me to 65K but would slow down the application.
Option 3 .Using jquery modify/create document.url options so that every filter option gets appended to the document.url and on reload I get the parameters set by the user for filtering.But this looks very cumbersome to implement.
Option 4. Using gems like rails temporary database etc.
I have opted with option 2 and using session store for the purpose but I think that it will become cumbersome to maintain this in the future.
Just need some suggestions like what do other rails ecommerce websites do to solve this problem or is there any better way to solve this.

Redis
What I'd do is add a layer of abstraction; specifically I think you'd benefit from using Redis, or similar temporary db (as you alluded to in your question).
Redis is a key:value database, which basically stores JSON values for you to use within your app. If you tie it to a model, you'll be able to store temporary values without hindering your app's performance.
I think you could setup Redis to store a guest id, and an array of your values from that:
[guest_user_id] => [
1 => "x"
2 => "y"
3 => ["z", "a", "b"]
]
You'd be able to generate the guest_user_id when you initialize the Redis system, and store it in the user's session. This way, you're only storing minimal data inside your user's browser, and can populate the various controller actions with Redis data:
#config/routes.rb
resources :categories do
resources :subcategories
end
#app/models/user.rb
Class User < ActiveRecord::Base
def self.new_data
# create guest_id and send to Redis
end
end
This will allow you to populate a session with your guest_id if the user is not registered:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
def show
#user = user_signed_in? ? current_user : User.new_data
#You'll then be able to populate their Redis values with the data from the product selection etc
end
end
I could go into more specifics, but as you're only looking for suggestions, this is what I have to recommend at the moment

Related

Rails session data - Store in a hash

I have a form on a rails view that submits data to a page that will represent a shopping cart summary page.
When I submit the data to the next page they are transmitted as follows according the console output.
"team"=>{"team_name"=>"Joe Blogs", "email"=>"joe#bloggs.com", "player1"=>"
3", "player2"=>"4", "player3"=>"5"}
I want to store this data is a session variable namely as a hash so if another team gets submitted to the summary page I can add it to the session as another hash entry. i.e. team[1], team[2].
Then I can access team[1].team_name, etc. and use it accordingly.
In summary, I want a user to be able to fill out a form and have it put into their cart. They can then go back and do the same again. Finally the can look at their cart and remove any records they don't want, clear the cart or submit what they choose into the database.
I can't find out how to do it or if it's even possible.
Any solutions or suggestions on how to implement this?
You can easily store a hash in Rails session.
Example:
class SomeController < ApplicationController
def some_action
session[:cart] = {"team_name"=>"Joe Blogs", "email"=>"joe#bloggs.com", "player1"=>"3", "player2"=>"4", "player3"=>"5"}
end
end
But, by default, Rails stores sessions in cookies, and a cookie size is limited to just 4 kilobytes of data, so if your hash is going to contain more than a few keys, you will need to use something else for session storage, e.g. the database.
To store session in the database you can use the activerecord-session_store gem.

Migrating sessions from cookie_store to Redis in Rails 3

I need to switch the session store in my Rails 3 app from cookie_store to redis-session-store. There are many reasons for this (security, SSO with other Rails and non-Rails apps). Are there any best practices on how to do it without loosing all current sessions?
What i could imagine is a two steps approach:
Collect all user sessions for N days and store them in the DB or in Redis (update if already stored).
Use stored user sessions to create entries in Redis.
Alternatively, on the fly migration would also be possible. Means read cookies, use secret key to decrypt the session data and store it as a new session in Redis.
I realize this ticket is pretty old, but this may help others. We ended up changing our session store to Redis, but then still looking for the legacy cookie (for a week or two) before no longer respecting them.
There are probably some security concerns to consider before using this strategy - you want to make sure those risks are worth it compared to the cost of having to sign your entire user-base out all at once. With Rails, the cookies are encrypted and can't be tampered with.
Here's what we used:
class SessionsController < Devise::SessionsController
LEGACY_COOKIE_NAME = "_old_session_name".freeze
def new
return if detect_valid_cookie
super
end
private
def detect_valid_legacy_cookie
legacy_cookie = request.cookie_jar.encrypted[LEGACY_COOKIE_NAME].presence || {}
valid_user_id = legacy_cookie['warden.user.user.key'].try(:first).try(:first)
return unless valid_user_id
user = User.find_by(:id => valid_user_id)
return unless user
if sign_in user
request.cookie_jar.delete(LEGACY_COOKIE_NAME)
redirect_to root_path # or whever you want
true
else
false
end
end
end
Stolen from here:
http://www.happybearsoftware.com/almost-protect-yourself-from-cookie-session-store.html (the last two sections)
Basically, use this:
Rails.application.config.action_dispatch.cookies_serializer = :hybrid
Quote follows:
This will cause Rails to accept sessions serialized with Marshal and exchange them for sessions serialized with JSON.
After you're confident that all your users sessions have been converted to JSON, you can roll out another release that flips the config value to :json.
Note: If you're storing complex Ruby objects in the session and need them to be serialized with Marshal, you won't be able to use the JSON serializer.

Rails fragment caching with personalization

My site has a top navigation menu bar which has infrequently changed contents and it's therefore a good candidate for fragment caching. But if a user is signed in, part of the menu bar will have the user's name in it, and is therefore personalised and can't be cached.
I can either:
Fragment cache the static part and serve up the personalised part separately. That seems pretty easy.
Cache the lot, possibly in memcached or CloudFront, keep the user's name in the session, and use JavaScript to extract the user's name from the session and personalise the page at the client end.
What is the best option to go with? Preferably based on personal experience of similar situations.
Try this:
##in user.rb to cache user
after_save :invalidate_cache
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
Rails.cache.fetch("user:#{single_key}") do
User.where(:id => single_key).entries.first
end
end
def invalidate_cache
Rails.cache.delete("user:#{id}")
end
Credit: Rails Devise: How do I (mem)cache devise's database requests for the user object?

Logging Feature Usage in Rails App

I'm interested in logging information about what features users are using in my Rails app. I know of existing Gems for performance metrics, logging user activity (e.g., public_activity), or more general analytics (e.g., google analytics), but what I'm looking for is a bit difference.
For example, if I have a feature that enables users to export their data, what service would you recommend to keep track of how many users (and which users) are using this feature?
Simple option would of course be to write a another model (for example Activity) that keeps track of the usages:
class Activity < ActiveRecord::Base
# attributes for resource (export, in this case), action type (Excel export) and user id
end
Then it could be applied with before_filter on your ExportController:
ExportController < ApplicationController
before_filter :register_activity
def register_activity
ExportActivity.create { } # Apply the params here
end
end
This way also provides you with the possibility to easily access the data and make statistics about it as you have it under your own control.

Prevent modification ("hacking") of hidden fields in form in rails3?

So lets say I have a form for submitting a new post.
The form has a hidden field which specify's the category_id. We are also on the show view for that very category.
What I'm worried about, is that someone using something like firebug, might just edit the category id in the code, and then submit the form - creating a post for a different category.
Obviously my form is more complicated and a different scenario - but the idea is the same. I also cannot define the category in the post's create controller, as the category will be different on each show view...
Any solutions?
EDIT:
Here is a better question - is it possible to grab the Category id in the create controller for the post, if its not in a hidden field?
Does your site have the concept of permissions / access control lists on the categories themselves? If the user would have access to the other category, then I'd say there's no worry here since there's nothing stopping them from going to that other category and doing the same.
If your categories are restricted in some manner, then I'd suggest nesting your Post under a category (nested resource routes) and do a before_filter to ensure you're granted access to the appropriate category.
config/routes.rb
resources :categories do
resources :posts
end
app/controllers/posts_controller
before_filter :ensure_category_access
def create
#post = #category.posts.new(params[:post])
...
end
private
def ensure_category_access
#category = Category.find(params[:category_id])
# do whatever you need to do. if you don't have to validate access, then I'm not sure I'd worry about this.
# If the user wants to change their category in their post instead of
# going to the other category and posting there, I don't think I see a concern?
end
URL would look like
GET
/categories/1/posts/new
POST
/categories/1/posts
pst is right- never trust the user. Double-check the value sent via the view in your controller and, if it does't match something valid, kick the user out (auto-logout) and send the admin an email. You may also want to lock the user's account if it keeps happening.
Never, ever trust the user, of course ;-)
Now, that being said, it is possible to with a very high degree of confidence rely on hidden fields for temporal storage/staging (although this can generally also be handled entirely on the server with the session as well): ASP.NET follows this model and it has proven to be very secure against tampering if used correctly -- so what's the secret?
Hash validation aka MAC (Message Authentication Code). The ASP.NET MAC and usage is discussed briefly this article. In short the MAC is a hash of the form data (built using a server -- and perhaps session -- secret key) which is embedded in the form as a hidden field. When the form submission occurs this MAC is re-calculated from the data and then compared with the original MAC. Because the secrets are known only to the server it is not (realistically) possible for a client to generate a valid MAC from the data itself.
However, I do not use RoR or know what modules, if any, may implement security like this. I do hope that someone can provide more insight (in their own answer ;-) if such solutions exist, because it is a very powerful construct and easily allows safe per-form data association and validation.
Happy coding.

Resources