I'm attempting to create a shopping cart on my Rails 7 app, but for some reason, it's not working.
The basic setup is there are many products. When a new session is created, a blank order should be created, to which a customer can add order_items (which is basically a has_many :through to join products and orders), with #order representing the current order that is active at any given session.
The problem is that #order.id is coming up as nonexistant...but it's not nil...because #order doesn't actually exist. I'm not sure why this is happening because the set_order method should get called before everything.
I have the following in my application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :set_order
private
def set_order
#order = Order.find(session[:order_id])
rescue ActiveRecord::RecordNotFound
#order = Order.create
session[:order_id] = #order.id
#order
end
end
Can anyone see where I'm going wrong here? I've looked at a bunch of other SO posts and all of it seems similar to what I have.
Related
I have an Angular 2 app that is using a Rails 5 API to create projects (think posts). I'm trying to add the current_user id to a new project (see the attempt in the Projects controller) when created but I get NoMethodError (undefined method projects' for nil:NilClass): app/controllers/projects_controller.rb:18:increate'. Line 18 is the line that attempts to use current_user when creating the project. If I remove current user and make it Project.new or if I hard code the user_is on the client side, there are no issues but with many users, this would be a problem. In the end, I want to know how to get the current user id and insert it when creating the project.
Application Controller
class ApplicationController < ActionController::API
include DeviseTokenAuth::Concerns::SetUserByToken
def current_user
#current_user
end
end
Projects Controller
def create
#project = current_user.projects.new(project_params)
if #project.save
render :show, status: :created, location: #project
else
render json: #project.errors, status: :unprocessable_entity
end
end
Looks like you haven't called the authenticate_user! method that sets the current_user.
Perhaps try making your ApplicationController look something like this...
class ApplicationController < ActionController::API
include DeviseTokenAuth::Concerns::SetUserByToken
before_action :authenticate_user!
end
The before_action call here ensures that the current_user method returns a value.
Resolved. Issue was than angular2-token wasn't passing correct headers for subsequent requests to non-auth routes such as my projects routes. current_user is good to go now.
Rails noob here.
I'm building a basic shopping cart and it was working perfectly before. Without changing any code (I git reset --hard to my prev commit where it was working) it broke. (?!?) Here's the breakdown:
Github Repo: https://github.com/christinecha/michaka
Creates a product. ✓
Adds Product ID to a new Order Item. ✓
Adds Order Item to an Order. ✓
--
Possible Issues
! - New Orders keep being created as you create Order Items = cart is always empty.
! - Cart is not connecting to the right Order ID
! - New sessions are being triggered = new Orders = problem
--
ORDER ITEMS CONTROLLER
class OrderItemsController < ApplicationController
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.save
session[:order_id] = #order.id
end
def update
#order = current_order
#order_item = #order.order_items.find(params[:id])
#order_item.update_attributes(order_item_params)
#order_items = #order.order_items
end
def destroy
#order = current_order
#order_item = #order.order_items.find(params[:id])
#order_item.destroy
#order_items = #order.order_items
end
private
def order_item_params
params.require(:order_item).permit(:quantity, :product_id)
end
end
SESSION_STORE.RB
Rails.application.config.session_store :cookie_store, key: '_bead-project_session'
ORDER MODEL
class Order < ActiveRecord::Base
belongs_to :order_status
has_many :order_items
before_create :set_order_status
before_save :update_subtotal
def subtotal
order_items.collect { |oi| oi.valid? ? (oi.quantity * oi.unit_price) : 0 }.sum
end
def subtotal_cents
subtotal * 100
end
private
def set_order_status
self.order_status_id = 1
end
def update_subtotal
self[:subtotal] = subtotal
end
end
APPLICATION CONTROLLER
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :current_order
def current_order
if !session[:order_id].nil?
Order.find(session[:order_id])
else
Order.new
end
end
end
It looks like ProductsController#create is called twice, once with format html and once as json.
I think you're submitting some of your data via ajax but still doing a post request from the form. However your controller, in it's format.html response is redirecting before all of the javascript actions have completed.
Since you only save #order and set the session from OrderItemsController#create which is called by js after your initial ajax().success, it is incomplete when the redirect is received.
What I think happens on click:
ajax post request AND regular form post
ajax success -> submit #order_item_product_id form
redirected by original form post response
I would suggest either redesigning the submit process to submit through regular form post or entirely through js. For example you could disable post from the form and change OrderItemsController#create to finally redirect (via js) render :js => "window.location.href = '/cart';"
I've built a shopping cart and it works great (thanks to this tutorial). Now I'm trying to modify it to my needs. I would like an order to be created based on the seller_id attribute so that a shopper can see multiple orders from different sellers in their shopping cart similar to eBay or Etsy:
This is in my order_items_controller:
class OrderItemsController < ApplicationController
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.seller_id = #order_item.seller_id
#order.save
session[:order_id] = #order.id
end
This is in my application_controller:
def current_order
if !session[:order_id].nil?
Order.find(session[:order_id])
else
Order.new
end
I thought that changing the definition of current_order to:
def current_order
Order.find_or_create_by(seller_id: :seller_id)
end
would solve things but it creates duplicate orders. I've tried several other variations and all produce errors. Can someone help me out? Even just on how to think about this differently?
how to make this code clean in rails?
profiles_controller.rb :
class ProfilesController < ApplicationController
before_action :find_profile, only: [:edit, :update]
def index
#profiles = Profile.all
end
def new
#profile = Profile.new
end
def create
profile, message = Profile.create_object(params["profile"], current_user)
flash[:notice] = message
redirect_to profile_url
end
def edit
end
def update
profile, message = #profile.update_object(params["profile"])
flash[:notice] = message
redirect_to profile_url
end
private
def find_profile
#profile = Profile.friendly.find(params["id"])
end
end
i look flash[:notice] and redirct_to profile_url is duplicate in my code, how to make the code to clean and dry?
How about moving the repetitive code to a separate method and call that method inside the actions.
def flash_redirect # you can come up with a better name
flash[:notice] = message
redirect_to profile_url
end
then in update action:
def update
profile, message = #profile.update_object(params["profile"])
flash_redirect
end
do the same thing for create action
UPDATE:
in case you are wondering about usingafter_action, you can't use it to redirect as the call-back is appended after the action runs out its course. see this answer
Take a look at Inherited Resources. It's based on the fact that many CRUD controllers in Rails have the exact same general structure. It does most of the work for you and is fully customisable in case things are done a little different in your controllers.
Using this gem, your code would look like this:
class ProfilesController < InheritedResources::Base
def create
redirect_to_profile(*Profile.create_object(params[:profile], current_user))
end
def update
redirect_to_profile(*#profile.update_object(params[:profile]))
end
private
def redirect_to_profile(profile, message)
redirect_to(profile_url, notice: message)
end
def resource
#profile ||= Profile.friendly.find(params[:id])
end
end
The create and update methods return multiple values, so I used the splat operator to DRY this up.
create_object and update_object don't follow the Rails default, so we need to implement those actions for Inherited Resources instead. Currently they don't seem to be handling validation errors. If you can, refactor them to use ActiveRecord's save and update, it would make everything even easier and DRYer.
I have stumbled upon a situation where my application looks for an id that does not exist in the database. An exception is thrown. Of course, this is a pretty standard situation for any web developer.
Thanks to this answer I know that using rescue deals with the situation pretty neatly, like so:
def show
#customer = Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound #customer with that id cannot be found
redirect_to action: :index #redirect to index page takes place instead of crashing
end
In case the customer cannot be found, the user gets redirected to the index page. This works absolutely fine.
Now, this is all nice, but I need to do the same rescue attempts in actions like show, edit, destroy, etc, i.e. every controller method that needs a specific id.
Having said that, here's my question:
Isn't there any way to generally tell my controller that if it can't find the id in any of its methods, it shall redirect to the index page (or, generally, perform a specific task)?
You must use rescue_from for this task. See example in the Action Controller Overview Guide
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
redirect_to action: :index
end
end
Rails has a built-in rescue_from class method:
class CustomersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :index
...
end
If you're talking about doing this within a single controller (as opposed to doing this globally in every controller) then here are a couple options:
You can use a before_filter to setup your resource:
class CustomerController < ApplicationController
before_filter :get_customer, :only => [ :show, :update, :delete ]
def show
end
private
def get_customer
#customer = ActiveRecord.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
end
Or you might use a method instead. I've been moving in this direction rather than using instance variables inside views, and it would also help you solve your problem:
class CustomerController < ApplicationController
def show
# Uses customer instead of #customer
end
private
def customer
#customer ||= Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
helper_method :customer
end
In certain cases, I would recommend that you use Model.find_by_id(id) as opposed to Model.find(id). Instead of throwing an exception, .find_by_id returns nil. if the record could not be found.
Just make sure to check for nils to avoid NoMethodError!
P.S. For what it's worth, Model.find_by_id(id) is functionally equivalent to Model.where(id: id), which would allow you to build out some additional relations if you want.