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.
Related
I've got a number of security concerns about my current application and wondering if I am leaving myself open to abuse, in the following arenas.
a) .My main access control method is by maining a current_user, current_company current_project method in my application controller. These methods return object based on stored session keys established when a user logs in and cleared when they log out. I.e if I want to know something about the current user, I can call "current_user.role" or if I want see whether the account a user is trying to change belongs to him, I check whether the associated account id which is requested in the url actually belongs to that user, essentially as follows
in Account controller
def account_info
redirect_to login_path if !user.logged_in
account_id=params[:account_id]
#account = Account.find(account_id)
unless account_belongs_to_user(account_id)
redirect_to unauthorized_path
end
end
In my application controller, when a user is initially authenticated, I do something like this:
session[:current_user_id] = user.id
and clear that session key when the user logs out.
Then when account is requested, and account_belongs_to_user is called, the application controller processes it, more or less like this:
def account_belongs_to_user(account_id)
account = Account.find(account_id)
return account.user_id==session[:current_user_id]
end
So I guess my security scheme ultimately relies on whether the session data is secure and not trivially spoofable.
b) When I render pages I sometimes pass objects which have senstive data to my erb pages to generate the page text.
For example, I might pass a "company" object (ActiveRecord) to the view to generate an invoice screen. But the company object, passed as #company, has a lot of sensitive data like access keys and the like. Not really being fully aware of the the internals, if I don't specifically include something like:
<%= #company.access_token %>
on my web page, can I be confident that the attributes of #company won't somehow be passed into the browser unless I specifically ask for them to be rendered on the page?
This is obviously an issue when using rails to serve data for say, AngularJS single page applications, as everything I pass for Angular to render the page I assume is probably accessible to an evil-doer even if not on the page itself, but I'm hoping that's not the case with pages generated server side by rails.
This may be a naive question, but thanks as I just want to be certain what I am doing before start spilling secrets all over the place.
put an authentication for the token using active_record callback
https://guides.rubyonrails.org/active_record_callbacks.html
I am creating search form for model with some tabs, radiobuttons, dropbox, etc. I use index action for searching and sorting. Parameters for searching is persisted in params. It works while I stay in the same index view. But if I had to visit other page and then come back to search page again, params doesn't work and state is not persisted.
I know I can do it with session, but suppose I would have more search forms for another models and so all that params goes into session. Doesn't it make session messy?
So question is:
Is there other reasonable way to persist page state (but I don't want to put into database)
You might consider a class in your app to help manage data input/output, somewhat similar to ActiveRecord's database interface. You can store this data either in a session maybe memcache or redis server would be better. Here's an example.
class SearchParam
def initialize(id, model, query_string = nil)
#id = id
#model = model
#query_string = query_string
end
def save
# write #query_string to redis using `key`
end
def self.find(id, model)
instance = new(id, model)
instance.query_string = Redis.get(instance.key)
return instance
end
def query_string
#query_string
end
def query_string=(qstring)
#query_string=qstring
end
def key
"#{#id}-#{model}"
end
end
I forget the exact redis commands and syntax, but you'll get the idea if you've worked with it before - basic writing and reading. Memcache or session would also work. Then you can easily store the hash representation of the query string for a user/model combo and set your search params for use in the search form.
Easy to lookup based on a session id in the controller when they first get to the index page (if there are no params submitted)
#search_params = SearchParam.find(session.id, model).query_string
Or to save the new query_string when you return results based on the form submit in the index action
SearchParam.new(session.id, model, params).save
There is also localStorage (https://developer.mozilla.org/en/docs/Web/API/Window/localStorage) available in browser, but you have to use JS to access it.
Otherwise, I think it is okay to store it in session, unless you have like tens of different search forms.
My Rails app depends on the Rails cache to temporarily hold user input and pass it between controller actions during the user log in process. However, I realized that I have made a pretty serious error (since I am super-new to Rails and MVC, in general) and could use some help/advice/wisdom - basically, if two users are simultaneously (or nearly simultaneously) submitting data and going through the controller steps below, there's going to be some trouble - lost data, one user's data being entered as another, etc. When I was building this, I did not understand the nature of controllers, though I for sure have a better idea now....
In short, here's how the code below is intended to work: a non-logged-in user fills out a form and submits that data which is passed to the submission action and held in a Rails cache object called incoming_report; the user is then prompted to log in (via Devise), and a successful log in triggers the approval controller action which reads the incoming_report cache object, saves that data to the database, and then creates a new cache object called ids to hang on to the id #'s of the user's entries; this redirects to the summary action which gathers up those ids and shows the corresponding data to the user as a summary of their entry.
report_controller.rb
#ON SUBMITTING THE FORM...
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#SUBMITTED DATA IS HELD IN THE CACHE AS 'incoming_report' TO PASS TO approval CONTROLLER ACTION ON SUCCESSFUL LOGIN
Rails.cache.write("incoming_report",#incomingReport)
end
#ON SUCCESSFUL LOG IN...
def approval
#incomingReport = Rails.cache.read("incoming_report")
#newReportIDArray = Array.new
#incomingReport.each do |x|
#DATA PROCESSING STUFF HERE...
end
#DELETE 'incoming_report' FROM CACHE
Rails.cache.delete("incoming_report")
#ID NUMBERS OF ENTRIES ARE HELD TO PASS TO summary CONTROLLER ACTION
Rails.cache.write("ids",#newReportIDArray)
redirect_to report_summary_path
end
#AFTER DATA IS ENTERED...
def summary
#newReportIDs = Rails.cache.read("ids")
#DELETE 'ids' FROM CACHE
Rails.cache.delete("ids")
end
For the most part, this works very well, but if users cross each other during any of these actions, it's disastrous. For instance, if one user hits the submission action while another user is busy logging in, the incoming_report object of the second user will overwrite that of the first user... and so on and so forth.
The main source of this problem is the fact that users log in after they have clicked the form submit button - the app must be structured that way - and the data can't be held in local/session storage objects because it includes base64 strings and is too long to employ those methods in certain browsers. Holding this data on the server seemed like the best course of action until I noticed this issue...
Is there any way that I can prevent users from getting in each other's way in the controllers? Or have I made a pretty fatal error?
Editing to show solution
Ultimately, using session variables instead of the Rails cache saved the day. Here's the reconfigured controller to show the solution...
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#newReportIDArray = Array.new
#incomingReport.each do |x|
#DATA PROCESSING STUFF HERE
#newReportIDArray.push(#new_report.id)
end
session[:ids] = #newReportIDArray
respond_to do |format|
format.json do
render json: {
success: 200
}.to_json
end
end
end
def approval
#reportIDs = session[:ids]
#reportIDs.each do |x|
#new_report = Report.find(x)
#new_report.user_id = current_user.id
#new_report.save
end
redirect_to report_summary_path
end
def summary
#reportIDs = session[:ids]
end
Many thanks to the two folks who helped me on this and showed me the ways of the session variable.
I ran into a problem with the session variable being 'lost' between controller actions - Rails / Devise - updating session variables between controller actions
I would suggest that you store the initial information into the database. From there I would either save the id in a cookie or session variable. You would then be able to retrieve the information from the database in the approval controller. Another option I would consider would be to generate a guid as an id and pass that along to the sign in page as a query string parameter. Store the guid in a hidden input tag, under the form tag. Then when the user submits their credentials, you can also pass the guid. At that point you log them in. If successful, pull the guid from the posted data and then retrieve the database information with the guid.
Wade
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
I am creating a checkout for an online store. When the customer has filled out his shipping and billing info, it takes them to an order summary page. Here's my action controller for that:
def order_summary
#cart = current_or_guest_user.cart
#purchase = current_or_guest_user.incomplete_purchases.last
if #purchase.shipping_method.uses_ups?
products = #cart.cart_items.map(&:product)
ups_shipping = UpsShipping.new(products: products, purchase: #purchase)
#shipping_rate = ups_shipping.request_shipping_rate
else
#shipping_rate = UnitedStatesPostalServiceShipping.calculate_shipping_rate(sub_total: #cart.total)
end
end
UpsShipping and UnitedStatesPostalServiceShipping are non ActiveRecord models that I wrote to hit external APIs to request a shipping rate. They don't point to any tables in my database.
This they don't have any records in the database, I need a way to persist the value of the #shipping_rate response in the request that follows order_summary and store it as #purchase.shipping_cost. I'm thinking of either just storing it in the session or putting a hidden field in the view.
Would it be safe for me to have a <%= hidden_field_tag "purchase[shipping_cost]" #shipping_rate %> tag in the view? Or would putting that in the session be safer?
Or is hitting the UPS/USPS API another time in the following request the best choice? I am thinking that hitting the APIs again would make my app a little more fragile, which is why I only want to hit it once and store the value. Thoughts on this?