Rails: How to save a cart to a user session? - ruby-on-rails

In my application I have designed a cart while following a similar format to the layout in the book Agile Web Development With Rails 4 and I have a slight problem. So a user can add items to a cart, view their cart, and see price of each item and also the total. The issue I ran into is when a user puts items in a cart and then signs out, the cart keeps the items in the cart even when a different user signs in. I believe the proper way would be that each user would have their own individual cart.
Here is my user model and controller
class User < ActiveRecord::Base
has_one :cart
# :confirmable, :lockable, :timeoutable and :omniauthable
# :confirmable, :lockable, :timeoutable and :omniauthable
has_secure_password
validates :email, presence: true
end
class UsersController < ApplicationController
def new
#user = User.new
end
def show
#user = User.find(params[:id])
end
def create
#user = User.new(user_params)
if #user.save
session[:user_id] = #user.id
redirect_to #user
else
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to #user
end
end
private
def user_params
params.require(:user).permit(:first_name, :admin, :last_name, :email, :password, :password_confirmation, :phone_number, :address_one, :address_two, :city, :country, :state, :zip)
end
end
My cart model and controller
class Cart < ActiveRecord::Base
has_many :order_items
belongs_to :user
has_many :line_items, dependent: :destroy
def add_part(part_id)
current_part = line_items.find_by(part_id: part_id)
if current_part
current_part.quantity += 1
else
current_part = line_items.build(part_id: part_id)
end
current_part
end
def total_price
line_items.to_a.sum { |item| item.total_price}
end
end
class CartsController < ApplicationController
before_action :set_cart, only: [:show, :edit, :update, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def show
#cart = Cart.find(params[:id])
end
def edit
#cart = Cart.new(cart_params)
end
def update
#cart = Cart.find(params[:id])
if #cart.update_attributes(cart_params)
redirect_to #cart
end
end
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
respond_to do |format|
format.html { redirect_to root_path }
format.json { head :no_content }
end
end
private
def cart_params
params.require(:cart).permit(:user_id)
end
def invalid_cart
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to root_path, notice: "Invalid cart"
end
end
and my Line Items controller and current module (line items associates a part to a cart in my layout)
class LineItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_line_item, only: [:show, :edit, :update, :destroy]
def create
part = Part.find(params[:part_id])
#line_item = #cart.add_part(part.id)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart }
format.json { render action: 'show', status: :created,
location: #line_item }
else
format.html { render action: 'new' }
format.json { render json: #line_item.errors,
status: :unprocessable_entity }
end
end
end
end
module CurrentCart
extend ActiveSupport::Concern
private
def set_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
#cart = Cart.create
session[:cart_id] = #cart.id
end
end
Any advice on how I can get one cart to be associate with one user would be a big help :) if anymore information is needed just ask. Thanks again!

Hahaha! I did this whole book and never realized this bug!
Yeah... so the session stuff is cute, but you can always make it more secure by doing something like this:
def set_cart
#cart = Cart.find_by(id: session[:cart_id], user: session[:user_id])
rescue ActiveRecord::RecordNotFound
#cart = Cart.create
session[:cart_id] = #cart.id
end
Now I know you don't have the session[:user_id], but I'm guessing you already have a pretty good idea on how to get it done. ;)
Hint: On Sign In

Related

Friendly_id not showing up on rails

Hi guys im creating blog with friendly id, but its not working..
The problem is when i tried to edit the title, friendly id came up, but when i reopen, it just show the blog id. not the slug anymore
this is the code
in my model :
class Blog < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
def should_generate_new_friendly_id?
slug.blank? || title_changed?
end
end
my blog controller
class BlogsController < ApplicationController
before_action :set_blog, only: [:show, :edit, :update, :destroy]
def index
#blogs = Blog.all
end
def show
#blog = Blog.friendly.find(params[:id])
end
def new
#blog = Blog.new
end
def create
#blog = Blog.new(blog_params)
if #blog.save
redirect_to blog_path(#blog), notice: "Successfully Created"
else
render :new
end
end
def edit
end
def update
if #blog.update(blog_params)
redirect_to blog_path(#blog), notice: "Successfully Update"
else
render 'edit'
end
end
def destroy
#blog.destroy
redirect_to blog_path(#blog)
end
private
def set_blog
#blog = Blog.friendly.find(params[:id])
end
def blog_params
params.require(:blog).permit(:title, :body, :image, :category_id)
end
end
i spent so much time to solve this.. please help

Pundit::NotAuthorizedError - ApplicationPolicy inheritance doesn't work

I am installing pundit on my app. My RestaurantPolicy file inherits from the ApplicationPolicy one (which by default gives access to none of the methods). When changing the methods in RestaurantPolicy (from false to true) it seems to have no effect as I still have access to none of the methods. BUT when changing falses to trues in the ApplicationPolicy file...I have the accesses ! Any idea why methods in the RestaurantPolicy file do not override the ApplicationPolicy's ones ? Thank youuu!!
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
include Pundit
# Pundit: white-list approach.
after_action :verify_authorized, except: :index, unless: :skip_pundit?
after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
private
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
end
restaurants_controller.rb
class RestaurantsController < ApplicationController
before_action :set_restaurant, only: [:show, :edit, :update, :destroy]
def index
#restaurants = Restaurant.all
authorize #restaurants
end
def show
end
def new
#restaurant = Restaurant.new
authorize #restaurant
end
def edit
end
def create
#restaurant = Restaurant.new(restaurant_params)
#restaurant.user = current_user
# OU : #restaurant = current_user.restaurants.build(restaurant_params)
respond_to do |format|
if #restaurant.save
format.html { redirect_to #restaurant, notice: 'Restaurant was successfully created.' }
format.json { render :show, status: :created, location: #restaurant }
else
format.html { render :new }
format.json { render json: #restaurant.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #restaurant.update(restaurant_params)
format.html { redirect_to #restaurant, notice: 'Restaurant was successfully updated.' }
format.json { render :show, status: :ok, location: #restaurant }
else
format.html { render :edit }
format.json { render json: #restaurant.errors, status: :unprocessable_entity }
end
end
end
def destroy
#restaurant.destroy
respond_to do |format|
format.html { redirect_to restaurants_url, notice: 'Restaurant was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_restaurant
#restaurant = Restaurant.find(params[:id])
end
def restaurant_params
params.require(:restaurant).permit(:name)
end
end
restaurant.rb
class Restaurant < ApplicationRecord
belongs_to :user
end
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :restaurants
end
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope
end
end
end
restaurant_policy.rb
class RestaurantPolicy < ApplicationPolicy
class Scope < Scope
def index?
true
end
def show?
scope.where(:id => record.id).exists?
end
def create?
true
end
def new?
create?
end
def update?
true
end
def edit?
update?
end
def destroy?
true
end
end
end
You must define your policy methods out of RestaurantPolicy::Scope, like so:
class RestaurantPolicy < ApplicationPolicy
class Scope < Scope
def index?
true
end
end
# Notice we have closed the definition of Scope class.
def show?
scope.where(:id => record.id).exists?
end
def create?
true
end
def new?
create?
end
def update?
true
end
def edit?
update?
end
def destroy?
true
end
end

ruby on rails, issue with cart & current_user

i'm following the tutorial from Michael Hartl and created a shopping cart which i encountered few issues with.
each user can create a new shopping cart with different 'id', but when different user add product to cart, the added products adds in all carts of different 'id' instead of that particular cart by current_user
how to restrict user to only view their own cart, without able to view other user cart?
please guide to resolve issues above, much appreciated with thanks!
user.rb (not a complete code because it will be lengthy, added the 'has_one :cart' besides original codes from Michael Hartl tutorial)
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
has_many :orders
has_one :cart
cart.rb
class Cart < ActiveRecord::Base
has_many :line_items, dependent: :destroy
belongs_to :user
def add_product(product_id)
current_item = line_items.find_by(product_id: product_id)
if current_item
current_item.quantity += 1 #quantity of line_item, product in cart
else
current_item = line_items.build(product_id: product_id)
end
current_item
end
def total_price
line_items.to_a.sum { |item| item.total_price }
end
end
concerns/Current_Cart.rb
module CurrentCart
extend ActiveSupport::Concern
private
def set_cart
#cart = current_user.cart || current_user.create_cart
session[:cart_id] = #cart.id
end
end
line_items_controller.rb
class LineItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create] #before create, execute :set_cart, find(or create) cart
before_action :set_line_item, only: [:show, :edit, :update, :destroy]
def index
#line_items = LineItem.all
end
def show
end
def new
#line_item = LineItem.new
end
def edit
end
def create
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product.id)
if #line_item.save
redirect_to current_user.cart
else
render :new
end
end
def update
if #line_item.update(line_item_params)
redirect_to #line_item, notice: 'Line item was successfully updated.'
else
render :edit
end
end
def destroy
#line_item.destroy
redirect_to line_items_url, notice: 'Line item was successfully destroyed.'
end
private
def set_line_item
#line_item = LineItem.find(params[:id])
end
def line_item_params
params.require(:line_item).permit(:product_id)
end
end
carts_controller.rb
class CartsController < ApplicationController
before_action :set_cart, only: [:edit, :update, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def show
#cart = current_user.cart
end
def edit
end
def update
if #cart.update(cart_params)
redirect_to #cart, notice: 'Cart was successfully updated.'
else
render :edit
end
end
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
redirect_to store_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cart
#cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def cart_params
params.fetch(:cart, {})
end
def invalid_cart
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to store_url, notice: 'Invalid cart'
end
end
if im logged in as an user with id '1', i created my cart with id '1'. I logged out, sign in again with another account with id '2', created a cart with id '2', but when i access another cart with link cart/1, im still able to see the cart from another user which not suppose to happen. Hope u understand –
The reason you can view another individual's cart is due to the controller code.
Whenever you show a cart, first the controller sets the cart using set_cart from within the controller.
def set_cart
#cart = Cart.find(params[:id])
end
This will fetch whatever cart with a specific ID.
Then show will display any cart that is passed to it.
def show
#cart = current_user.cart
end
What you should be doing is using current_cart.rb to set the cart and remove the existing set_cart from the controller. Also, make set_cart in current_cart.rb public.
You will also need to change your show route, since it is expecting an :id, and now we're not telling the server which cart to view.
I forget exactly where the book includes CurrentCart, I believe it was in ApplicationController. If so, then before_action :set_cart, only[...] should work just fine with other logic.

Cannot find user without id?

i'm getting this error for my products and user table.
--Couldn't find user without an id
def set_user
#user = User.find(params[:user_id])
end
I have nested the routes like so..
resources :users do
resources :products do
resources :reviews
end
end
and here is my products controller..
class ProductsController < ApplicationController
before_action :require_signin, except: [:index, :show]
before_action :set_user
def index
#products = #user.products
end
def show
#product = Product.find(params[:id])
end
def edit
#product = Product.find(params[:id])
end
def update
#product = Product.find(params[:id])
if #product.update(product_params)
redirect_to [#user, #product], notice: "Product successfully updated!"
else
render :edit
end
end
def new
#product = #user.products.new
end
def create
#product = #user.products.new(product_params)
#product.user = current_user
if #product.save
redirect_to user_products_path(#product, #user), notice: "Product successfully created!"
else
render :new
end
end
def destroy
#product = Product.find(params[:id])
#product.destroy
redirect_to user_products_path(#product, #user), alert: "Product successfully deleted!"
end
private
def product_params
params.require(:product).permit(:title, :description, :posted_on, :price, :location, :category)
end
def set_user
#user = User.find(params[:user_id])
end
end
All i am trying to do is associate the user and product so the product belongs_to user, and the user has_many products.
class Product < ActiveRecord::Base
belongs_to :user
has_many :reviews
class User < ActiveRecord::Base
has_secure_password
has_many :reviews, dependent: :destroy
has_many :products, dependent: :destroy
As other users have mentioned the params[:user_id] value is probably nil.
That said, you already appear to have a current_user defined in the scope of the controller. I see it referenced in the create action. I'd bet that it was set by the require_sign_in before_action. Given what I think you are trying to do, it probably makes your set_user before_action a bit redundant.
You can probably just refer to current_user in your controller anywhere you are currently using #user. Alternatively, you might set #user = current_user in the set_user before_action.
SideNote:
Looking a bit closer at your create action:
def create
#product = #user.products.new(product_params)
#product.user = current_user
if #product.save
redirect_to user_products_path(#product, #user), notice: "Product successfully created!"
else
render :new
end
end
Correct me if I'm wrong but I believe doing something like #model.association.new sets the model_id for the newly created association object so I would change the two lines
#product = #user.products.new(product_params)
#product.user = current_user
to simply be
#product = current_user.products.new(product_params)
For any action of your controller you should pass user_id param.
The reason of error is params[:user_id] equal nil

Cancan restricting access to users when it shouldn't (Ruby on Rails)

I'm having problems with allowing admin users only to see and edit the users he created.
I have a tiered system: SuperUser > Admin > other users
My SuperUser can edit all users, but my Admin user can only edit himself. To try to fix this, I have a creator_id parameter that gives a creator_id to the new user that matches the id of the current user.
My controller for users:
class UsersController < ApplicationController
#CanCan resource will generate a 500 error if unauthorized
load_and_authorize_resource :user
# GET /users
# GET /users.json
def index
#users = User.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
# GET /users/1
# GET /users/1.json
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #user }
end
end
# GET /users/new
# GET /users/new.json
def new
#user = User.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# GET /users/1/edit
def edit
#user = User.find(params[:id])
#User.find(session[:user])
end
# POST /users
# POST /users.json
def create
#user = User.new(params[:user])
#user.creator = current_user
respond_to do |format|
if #user.save
format.html { redirect_to #user, notice: 'Registration successful.' }
format.json { render json: #user, status: :created, location: #user }
else
format.html { render action: "new" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# PUT /users/1
# PUT /users/1.json
def update
#user = User.find(params[:id])
##user = current_user
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to #user, notice: 'Successfully updated profile.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /users/1
# DELETE /users/1.json
def destroy
#user = User.find(params[:id])
#user.destroy
respond_to do |format|
format.html { redirect_to users_url }
format.json { head :no_content }
end
end
end
and my ability.rb file:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new #Guest user w/o account
#Permissions on what pages can be seen by which users, and what
#Users can do with those pages
if user.status == "Super User"
can :manage, :all
elsif user.status == "Admin"
can :manage, Book
can [:create,:new], User
can [:show, :update], User, :id => user.id
can :manage, User, :creator_id => user.id
end
end
end
I did check the database, and it correctly assigns the current user's id to the creator_id of the new user. I'm just stuck. Cancan keeps denying the permission of updating those users and I'm not sure why. Any help is appreciated!
EDIT
My user model:
class User < ActiveRecord::Base
has_many :books
has_many :listings
has_many :orders
belongs_to :organizations
belongs_to :creator, class_name: 'User'
attr_accessible :password, :email, :first_name, :last_name, :password_confirmation, :status, :username
acts_as_authentic
validates :first_name, :presence => true
validates :last_name, :presence => true
validates :username, :presence => true, :uniqueness => true
validates :email, :presence => true, :uniqueness => true
validates :status, :presence => true
end
Okay just reading your question again it looks like you want an administrator to have authoritative access to manage a user. In this case you could define something fairly similar in your application_controller
def correct_user
if !params[:id].nil?
#user.User.find_by_id(params[:id])
if current_user.status :admin
else
access_denied unless current_user?(#user)
end
end
end
What this does is allows an administrator to have access to all users accounts and if the user is not an administrator then they are denied access. You can enable this feature using the before_filter in your controllers so that you could do something like before_filter :correct_user, :only => [:edit, :show] this means that only the correct user can have access to these actions. So should you have a UserController like the following maybe:
class UsersController < ApplicationController
load_and_authorize_resource
before_filter :correct_user, :only => [:edit, :show]
..
....
.....
end
This example shows that as a correct user or an admin will have access to edit and show actions.
Try this.
def initialize(user)
user ||= User.new
if user.super_user?
can :manage, :all
elsif user.admin?
can [:create, :new], User
can [:show, :edit, :update], User do |usr|
id == usr.id
end
can :manage, User do |usr|
usr.creator_id == usr.id
end
end
end
In user model, add methods:
def has_status?(given_status)
status == given_status
end
def admin?
has_status? 'Admin'
end
def super_user?
has_status? 'Super User'
end

Resources