Rails session variable (shopping cart) - ruby-on-rails

I'm trying to save the items the customer can add to my shopping cart in a session variable.
Below is my index method in the cart controller. The ID of the item is saved in #idtemp. Next I create a new array session[:content]. Then I find the item with the ID saved in #idtemp and return it as an array, which is used in the view to display the table. The problem is, everytime an item is added and the index function is called, session[:contenc] is set to [], deleting the cart, which means that a newly added item overwrites the last one.
Now I want this fixed so that a newly added item is added to the array without overwriting the last one, but I dont know how. I tried to initialize the session variable outside of index but that didnt work somehow. I know this is piss easy but I am pretty exhausted and cant figure it out.
def index
#idtemp = session[:cart]
session[:content] = []
session[:content] = session[:content] << (Artikel.find([#idtemp]))
#cart = session[:content]
end
-----view:
<% #cart.each do |id,quantity| %>
...
...

If i was you i would store the cart and its contents in the database. Then all the session has to do is to remember either the cart id, or the id of the user, if they're logged in (once you've got the user you can get the cart). I'd set it up something like this (which you can adjust for your own schema - Artikle or whatever instead of Product)
class Cart
belongs_to :user
has_many :cart_products
class CartProduct
belongs_to :cart
belongs_to :product
#extra field - quantity
When the page loads, look to see if you have session[:cart_id]. if you do, you can load the cart, and its associated products if need be. If not, then create a cart object and set session[:cart_id] to that.
If you have a logged in user (ie session[:user_id] or somesuch) then you can set current_user to that user, and set their cart.
I would manage this via some protected methods in ApplicationHelper
#in application.rb
def current_user
if #current_user
return #current_user
elsif session[:user_id] && user = User.where(:id => session[:user_id]).first
#current_user = user
return #current_user
end
end
#there's a bit of repetition in here but it should give you the idea
def current_cart
unless #current_cart
if current_user
cart = current_user.cart || Cart.create(:user => current_user)
#current_cart = cart
elsif session[:cart_id]
#current_cart = Cart.where(:id => session[:cart_id]).first || Cart.create
else
#current_cart = Cart.create
end
end
return #current_cart
end
Now, whenever you want to refer to the current user, or the current cart, in your controller and view code, just say current_user or current_cart. The instance variables inside each method mean that if it's been set once in this request then it won't use the logic again - it will just use the one it already saved in the instance variable.

Ok, adapting your existing system, i would do it like this. We'll have one session variable, which is a hash storing the ids of Artikels and the quantity of each, with this structure: session[:cart] = {123 => 1, 456 => 2} where 123 and 456 are Artikel ids.
def add
session[:cart] ||= {}
session[:cart][params[:id]] ||= 0
session[:cart][params[:id]] += 1
end
def index
session[:cart] ||= {}
end
now in your view you can say
<% session[:cart].each do |artikel_id, quantity| %>
<% artikel = Artikel.where(:id => artikel_id).first %>
...

Here is what i dont understand:
def index
#idtemp = session[:cart] # #idtemp is now an ID e.g. '1'
session[:inhalt] = [] # session[:inhalt] is now an empty array []
addedarticle = Artikel.find(#idtemp) # addedarticle is an article e.g.:{ID=>'1',title=>'Mouse'}
session[:inhalt].concat([addedarticle]) # session[:inhalt] is now [{...}] where {...} = article
#cart = session[:inhalt] # #cart = [{...}]
end
This works. An item is added to the cart and #cart is an array containing this item, it can now be used in the view. The only thing that has to be done now is to make sure that the next added item wont override the old ond. You said i can simply replace session[:inhalt] = [] with session[:inhalt] ||= [],
however, doing this leads to an error:
NoMethodError in Cart#index
undefined method `title' for "#":String
<% #cart.each do |id| %>
<tr>
<td><%= image_tag id.Image_URL %></td>
<td>
<div class="title"><%= id.title %></div>
<br>
It seems to me that #cart is not properly "filled". Its supposed to be an array of articles (=hashes) e.g. [{id => 1, title ='mouse'}, {id => 2, title = 'whatever'}], so i can iterate over it with the each do method and display them. But according to the error it is trying to extract the title from a string. What could be the cause of that?
If I can fix this, I am done with the task. Btw, thanks for helping me out so far :)

Related

undefined method `write_reviews' for #<User::ActiveRecord_Relation:0xaadd320>

Hi i am having two tables named users and write_reviews ,i have created active association between them as user has_many write_reviews while write_reviews belongs_to user
in my method i am trying to do like this
def details
#user = User.all
#writereview = #user.write_reviews.all
respond_to do |format|
format.html
format.json { render json: #writereview }
end
in my write_review model it is defined like
class WriteReview < ActiveRecord::Base
belongs_to :user
end
but it is giving error
User.all returns array(ActiveRecord::Relation) that all users.
write_reviews relation is associated with single user instance.
user has many wirte_reviews, not users(User.all)
#user must be one User instance.
You have to call write_reviews on a User object, you can't do it on a ActiveRecordRelation. That's why you getting that error. You can do these instead:
#user = User.first # or something else that returns a single user
#writereview = #user.write_reviews.all
undefined method `write_reviews' for User::ActiveRecord_Relation:0xaadd320
The error is due to this line
#writereview = #user.write_reviews.all
Here #user is a collection of users records according to your code, so #user.write_reviews is wrong.
If you want get the write_reviews of a particular user, then you need to change #user = User.all to #user = User.find(params[:id])
User.all
Returns an array of users, while write_reviews available only on single user.
Actually, in your case you don't need to select write_reviews by users, instead you can simply do:
#write_reviews = WriteReview.all
But if you want to select write_reviews only for specific users, you can do it like so:
#users = User.where(status: 'active') # just an example
#write_reviews = WriteReview.where(user_id: #users.map(&:id))
so to first the code looks like this, in order to retrieve all users writereviews :
#users = User.all
so now #users contains all your users, nothing's complicated here. we re gonna declare a variable #writereviews as an array.
#writereviews = []
#users.each do |u|
#writereviews << u.writereviews.all
end
now, you have all the writereviews of each user. the first element is an array that holds all the writereviews of the first user. we'll put it like that
#writereviews[0] // is an array containing all the write reviews of your first user.
#writereviews[0][0] // is the nested array containing the FIRST writereview of your
FIRST user
#writereviews[0][0].user // will give you the user of that review.
so in order to retrieve users of those writereviews,I came with a loop inside an other.
#authors = []
#writereviews.each do |element|
element.each do |subelement|
#authors << subelement.user
break // so that it doesnt neet to loop over the other writereviews, since they will all belongs to the same user.
end
end
and thats it, you can use #authors to retrieve the users of each writereview, #authors[0] is the user of the #writereviews[0], all what you need to do, is use those variables in your template.
Hope this is more clear to you now !
for the json part :
#total = [] #each element of this array would contain a hash of _'user, reviews[]'_
wrev = {} # a hash that will contain the user, and its write reviews
wrev["user"] = ""
wrev["wrev"] = [] #reviews are an array
#users.each do |u|
wrev["user"] = u.email
wrev["wrev"] = []
wrev["wrev"] = u.writereviews.all
#total << wrev
wrev = {}
end
respond_to do |format|
format.html
format.json { render json: #total }
end

Add Session User Id Into Line_Items table when line item record is created - Ruby on Rails

I am trying to add User Session ID into Line_Items table. I have researched everywhere and couldn't find the answer and that is why had to post it here. This application successfully adds the Phone ID and the Cart ID into the Line_Items table but doesn't let me add user session ID when I try the similar method of Phone and Cart ID. See below for my current implementation:
Line_Items Controller:
def create
# current_cart method in application_controller.rb
#cart = current_cart
phone = Phone.find(params[:phone_id])
if phone.stock > 0
#line_item = #cart.add_phone(phone.id )
if #line_item.save
redirect_to :back, notice: " '#{phone.model}' has been added to your cart."
else
render action: "new"
end
else
redirect_to :back, notice: " Cannot add: '#{phone.model}' - stock level is too low."
end
end
Cart.rb Model:
def add_phone(phone_id)
current_item = line_items.find_by_phone_id(phone_id)
if current_item
current_item.quantity += 1
else
current_item = line_items.build(phone_id: phone_id)
current_item.quantity = 1
end
current_item.phone.stock -= 1
current_item.phone.save
current_item
end
# returns true if stock level is greater than zero
def can_add(phone)
phone.stock > 0
end
Please let me know if any further code needed.
The issue is that the model classes in Rails don't know anything about the controller nor the session. I'm presuming you are using some authentication mechanism that gives you a current_user in your session... so we have to get that into your add_phone method.
In the controller, I'd change this line:
#line_item = #cart.add_phone(phone.id)
to this:
#line_item = #cart.add_phone(phone, current_user)
(notice there are two changes there - one to pass the phone rather than its id, since you already found it, and one to pass the current user.
Then we want to change your add_phone method to look like this:
def add_phone(phone, current_user = nil)
# make sure your Item class initializes quantity to o, not nil.
current_item = line_items.find_or_initialize_by_phone_id(phone.id)
current_item.increment!(:quantity)
current_item.phone.decrement!(:stock)
current_item.user = current_user
current_item.phone.save
current_item
end
notice I'm setting the user to nil by default... that way, code you already have that doesn't provide that field will continue to work. I'm also simplifying your method a little bit with the find_or_initialize_by helper... avoids an 'if' test. Also, the increment and decrement helpers clean up the intent of the code a little.
You should make sure your line item class includes
belongs_to :user
If you find yourself in a situation where you need to know current_user for a lot of domain logic, you might want to check out the sentient_user gem.
Save
There could be a number of reasons why your user ID is not being inserted:
- Is `user_id` variable accessible from your model?
- How are you saving `user_id`?
- Where are you defining the `user_id` variable?
If you're using an instance method (like in Cart.rb) and something like Devise, you likely won't be able to access the current_user helper directly; you'll have to pass it through the method:
def add_user(user_id)
#user = User.find(user_id)
#etc
end
If you post errors or other code pertaining to the user object directly, we'll be in a much better position to help!
Refactoring
I think you'll benefit from the increment! & decrement! methods:
def add_phone(phone_id)
current_item = line_items.find_by_phone_id(phone_id)
if current_item
current_item.incremenet!(:quality)
else
current_item = line_items.build(phone_id: phone_id)
current_item.quantity = 1
end
current_item.phone.decrement!(:stock)
current_item.phone.save
current_item
end

Rails 3 cookies undefined

I have a Rails 3 blog. I want every Post to have a "Like" button. Clicking on it will save this action to the database and store to cookies on the user who has just liked the Post (for disallowing same action again).
I wrote a simple action to do this:
def like
render :nothing => true
id = params[:post_id]
cookies.permanent[:like_history] ||= []
unless cookies.permanent[:like_history].include? id
cookies.permanent[:like_history] << id
#post = Post.find(id)
#post.update_column(:likes, #post.likes + 1)
end
end
But I'm getting NoMethodError (undefined method '[]' for nil:NilClass) when I try to log things. It points to this line: cookies.permanent[:like_history] ||= [] as if cookies.permanent isn't an array.
Am I doing something wrong with cookies here?
Turns out, the ||= operator counts as "reading" by rails standards, which actually makes sense. You can't "read" with cookies.permanent[:symbol], that's for writing, you read with cookies[:symbol]. So I modified that line to read:
cookies.permanent[:like_history] = "" unless defined? cookies[:like_history]
I think you have something stored in cookies.permanent[:like_history] which is not an Array. So make it nil or covert to array using to_a and try your code.
def like
render :nothing => true
cookies.permanent[:like_history] = nil #or cookies.permanent[:like_history] = cookies.permanent[:like_history].to_a
id = params[:post_id]
cookies.permanent[:like_history] ||= []
unless cookies.permanent[:like_history].include? id
cookies.permanent[:like_history] << id
#post = Post.find(id)
#post.update_column(:likes, #post.likes + 1)
end
end
Once it works remove that line you added.

keep count on an object in rails for a session and display it

I'm trying to create an app that can keep count of a users correct answers and display them. How do I access this for a session and display it. I have the count set-up as global variable which is probably wrong to start with.
My app asks a random question and if the user clicks on the correct answer it reloads the page with a new question. If incorrect it directs them to a new page. How would I display the correct answer count?
My users and sessions are set-up like the ruby on rails tutorial by michael hartl.
controller.rb
##ruby_functs_count = 0
def ruby_functs
#symbols = {
"abort" => "Terminates program. If an exception is raised (i.e., $! isn't nil), its error message is displayed.",
"Array( obj)" => "Returns obj after converting it to an array using to_ary or to_a."
}
#random = []
#random2 = []
rando = #symbols.sort_by {rand}
rando = rando.each { |k,v| #random << k and #random2 << v}
#sym = #random2[0]
#answer = #random[0]
#sym2 = #random[0..10].sort_by {rand}
end
sessions_controller.rb
class SessionsController < ApplicationController
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def new
end
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_to user
else
render 'new'
end
end
ruby_functs.html.erb
<% for #random in #sym2
if #random == #answer
x = cards_ruby_functs_path %>
<li class="btn btn-small btn-info flash"><%= link_to "#{#random}", x %></li>
<% else
x = cards_wrong_path %>
<li class="btn btn-small btn-info flash"><%= link_to "#{#random}", x %></li>
<% end %>
<% end %>
pfhahf, i don't know where to start...
yes, it's a bad idea to use class variables like ##ruby_functs_count to store state. this is not a global variable, that would look like $ruby_functs_count (using global variables es even worse).
what you could do as a start:
use ids
when you know the id of the answer, you can store it easily and compare the results:
# application_controller
Question = Struct.new(:q, :a)
QUESTIONS = {
1 => Question.new("abort", "Terminates..."),
2 => Question.new("Array( obj)", "Returns..."),
}
this example uses a struct to represent the data and stores it in a constant, so that it is readable and with easy access like:
question = QUESTIONS[1]
question.q # => "about"
question.a # => "Terminates..."
you can then keep track of the user-interaction in some session field:
session[:answered] ||= [] # init the session key if it's empty
session[:answered] << params[:question_id] # store the id so that you can work on it
i hope that this helps you go further with your code.

Rails: checking modified fields

First, I've generated scaffold called 'item'
I'd like to check which fields of the item are modified. and I've tried two possible attempts, those're not work tho.
First Attempt!
def edit
#item = Item.find(params[:id])
#item_before_update = #item.dup
end
def update
#item = Item.find(params[:id])
# compare #item_before_update and #item here, but #item_before_update is NIL !!!
end
Second Attempt!
I looked for the way passing data from view to controller and I couldn't.
edit.html.erb
<% #item_before_update = #item.dup %> # I thought #item_before_update can be read in update method of item controller. But NO.
<% params[:item_before_update] = #item.dup %> # And I also thought params[:item_before_update] can be read in update mothod of item controller. But AGAIN NO
<% form_for(#item) do |f| %>
# omitted
<% end %>
Please let me know how to solve this problem :(
Attributes that have changes that have not been persisted will respond true to changed?
#item.title_changed? #=> true
You can get an array of changed attributes by using changed
#item.changed #=> ['title']
For your #update action, you need to use attributes= to change the object, then you can use changed and changed? before persisting:
def update
#item = Item.find(params[:id])
#item.attributes = params[:item]
#item.changed #=> ['title']
... do stuff
#item.save
end

Resources