How to solve NoMethodError for this case? - ruby-on-rails

So my rails app has a number of airport fares defined for each operator, as:
class AirportFare < ActiveRecord::Base
belongs_to :operator
end
class Operator < ActiveRecord::Base
has_many :airport_fares
end
My routes are defined as:
resources :operators do
resources :airport_fares
end
And following is my airport_fares_controller:
class AirportFaresController < ApplicationController
before_action :set_airport_fare, only: [:show, :edit, :update, :destroy]
before_action :set_operator
# GET /airport_fares
# GET /airport_fares.json
def index
#operator = Operator.find(params[:operator_id])
#airport_fares = Operator.find(params[:operator_id]).airport_fares
end
# GET /airport_fares/1
# GET /airport_fares/1.json
def show
end
# GET /airport_fares/new
def new
#airport_fare = Operator.find(params[:operator_id]).airport_fares.build
end
# GET /airport_fares/1/edit
def edit
end
# POST /airport_fares
# POST /airport_fares.json
def create
#airport_fare = Operator.find(params[:operator_id]).airport_fares.build(airport_fare_params)
respond_to do |format|
if #airport_fare.save
format.html { redirect_to #airport_fare, notice: 'Airport fare was successfully created.' }
format.json { render :show, status: :created, location: #airport_fare }
else
format.html { render :new }
format.json { render json: #airport_fare.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /airport_fares/1
# PATCH/PUT /airport_fares/1.json
def update
respond_to do |format|
if #airport_fare.update(airport_fare_params)
format.html { redirect_to #airport_fare, notice: 'Airport fare was successfully updated.' }
format.json { render :show, status: :ok, location: #airport_fare }
else
format.html { render :edit }
format.json { render json: #airport_fare.errors, status: :unprocessable_entity }
end
end
end
# DELETE /airport_fares/1
# DELETE /airport_fares/1.json
def destroy
#airport_fare.destroy
respond_to do |format|
format.html { redirect_to operator_airport_fares_path(operator_id: #operator.id), notice: 'Airport fare was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_operator
#operator = Operator.find(params[:operator_id])
end
def set_airport_fare
#airport_fare = AirportFare.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def airport_fare_params
params.require(:airport_fare).permit(:cabcat, :triptype, :triptime, :basedist, :basehr, :basechrg, :ratepkm, :waitingbtime, :waitingchrg, :notes, :operator_id)
end
end
My db schema says:
create_table "airport_fares", force: true do |t|
t.string "cabcat"
t.string "triptype"
t.string "triptime"
t.decimal "basedist", precision: 8, scale: 2
t.integer "basehr"
t.decimal "basechrg", precision: 8, scale: 2
t.decimal "ratepkm", precision: 8, scale: 2
t.integer "waitingbtime"
t.decimal "waitingchrg", precision: 8, scale: 2
t.text "notes"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "operator_id"
end
The problem is when I try this in my console: AirportFare.first.operator. This leads to the error:
NoMethodError: undefined method 'operator' for #<AirportFare>.
Where is the problem?

In new action you should do:
def new
#operator = Operator.find(params[:operator_id])
#airprot_fare = AirportFare.new
end
in your form:
<%= form_for [#operator, #airport_fare] do |f| %>
This should work.

It Should be operator.first.airport_fares. As you have has_many relationship on operators

Related

How to implement marketplace: admin side show complete orders with list of purchased items: A challenge I cannot tackle

I'm currently stuck on a problem that I do not know how to tackle. I am currently working on a marketplace and a user can successfully, browse items, add items to cart and successfully purchase them items that are in a cart. I'm trying to build an admin side for the sellers to show a list of items that have been purchased but I am struggling as I do not know how to implement.
I have had a few ideas but nothing I think of seems to work.
If anyone can solve this I would be entirely grateful!
database
create_table "carts", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "line_items", force: :cascade do |t|
t.bigint "product_id", null: false
t.bigint "cart_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity", default: 1
t.bigint "order_id"
t.index ["cart_id"], name: "index_line_items_on_cart_id"
t.index ["order_id"], name: "index_line_items_on_order_id"
t.index ["product_id"], name: "index_line_items_on_product_id"
end
create_table "orders", force: :cascade do |t|
t.string "name"
t.text "address"
t.string "email"
t.integer "pay_type"
t.boolean "complete", default: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "products", force: :cascade do |t|
t.string "title"
t.text "description"
t.decimal "price", precision: 8, scale: 2
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "category_id"
t.index ["user_id"], name: "index_products_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "username"
t.string "name"
t.boolean "admin", default: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "uid"
t.string "provider"
t.string "access_code"
t.string "publishable_key"
t.string "stripe_id"
t.boolean "subscribed"
t.string "card_last4"
t.string "card_exp_month"
t.string "card_exp_year"
t.string "card_type"
t.text "perk_subscriptions", default: [], array: true
t.string "s_name"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
cart_controller
class CartsController < ApplicationController
before_action :set_cart, only: [:show, :edit, :update, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def index
#carts = Cart.all
end
def show
end
def new
#cart = Cart.new
end
def edit
end
def create
#cart = Cart.new(cart_params)
respond_to do |format|
if #cart.save
format.html { redirect_to #cart, notice: 'Cart was successfully created.' }
format.json { render :show, status: :created, location: #cart }
else
format.html { render :new }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #cart.update(cart_params)
format.html { redirect_to #cart, notice: 'Cart was successfully updated.' }
format.json { render :show, status: :ok, location: #cart }
else
format.html { render :edit }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session["cart_id"] = nil
respond_to do |format|
format.html { redirect_to store_index_url, notice: 'Cart was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_cart
#cart = Cart.find(params[:id])
end
def cart_params
params.fetch(:cart, {})
end
def invalid_cart
logger.error "Attempt to access invalid cart #{params[:id]}"
redirect_to store_index_url, notice: 'Invalid cart'
end
end
lineitem controller
class LineItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
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)
respond_to do |format|
if #line_item.save
format.html { redirect_to(request.env['HTTP_REFERER']) }
format.js {#current_item = #line_item}
format.json { render :show,
status: :created, location: #line_item }
else
format.html { render :new }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #line_item.update(line_item_params)
format.html { redirect_to #line_item, notice: 'Line item was successfully updated.' }
format.json { render :show, status: :ok, location: #line_item }
else
format.html { render :edit }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
def destroy
#line_item.destroy
respond_to do |format|
format.html { redirect_to line_items_url, notice: 'Line item was successfully destroyed.' }
format.json { head :no_content }
end
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
order controller
class OrdersController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:new, :create]
before_action :ensure_cart_isnt_empty, only: :new
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
#orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
#order = Order.new
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
#order.add_line_items_from_cart(#cart)
respond_to do |format|
if #order.save
Cart.destroy(session[:cart_id])
session[:cart_id] = nil
format.html { redirect_to store_index_url, notice: 'Thank you for your order' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if #order.update(order_params)
format.html { redirect_to #order, notice: 'Order was successfully updated.' }
format.json { render :show, status: :ok, location: #order }
else
format.html { render :edit }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
#order.destroy
respond_to do |format|
format.html { redirect_to orders_url, notice: 'Order was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def order_params
params.require(:order).permit(:name, :address, :email, :pay_type)
end
def ensure_cart_isnt_empty
if #cart.line_items.empty?
redirect_to store_index_url, notice: 'Your cart is empty'
end
end
end
product controller
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
# GET /products
# GET /products.json
def index
#products = Product.all
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
#product = Product.new
#product.user = current_user
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
def create
#product = Product.new(product_params)
#product.user_id = current_user.id
respond_to do |format|
if #product.save
if current_user.can_receive_payments?
UploadProductJob.perform_now(#product)
end
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :price, :category_id)
end
end
cart model
class Cart < ApplicationRecord
has_many :line_items, dependent: :destroy
def add_product(product)
current_item = line_items.find_by(product_id: product.id)
if current_item
current_item.quantity += 1
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
line_item model
class LineItem < ApplicationRecord
belongs_to :order, optional: true
belongs_to :product
belongs_to :cart, optional: true
def total_price
product.price.to_i * quantity.to_i
end
end
order model
class Order < ApplicationRecord
has_many :line_items, dependent: :destroy
enum pay_type: {
"Check" => 0, "Credit card" => 1, "Purchase order" => 2
}
validates :name, :address, :email, presence: true
validates :pay_type, inclusion: pay_types.keys
def add_line_items_from_cart(cart)
cart.line_items.each do |item|
item.cart_id = nil
line_items << item
end
end
end
product model
class Product < ApplicationRecord
belongs_to :user
belongs_to :category
has_many :line_items
before_destroy :ensure_not_referenced_by_any_line_item
private
def ensure_not_referenced_by_any_line_item
unless line_items.empty?
errors.add(:base, 'Line Items present')
throw :abort
end
end
end
You can tackle it. :)
Assuming a User is a seller, who has many Product, of which you want to show Product that have many LineItem that are associated to a complete Order:
current_user.products.joins(line_items: [:order]).where(orders: { completed: true })
This will require that your associations are correct.
What this is effectively doing:
orders = Order.where(completed: true)
line_items = LineItems.where(order: orders)
products = current_user.products.where(id: line_items.pluck(:product_id)
But because you can do that in one call to your database (make sure your indexes are correct), you should.
Use this code:
LineItem.joins(:orders).where('orders.complete' => true)
It will show you only LineItem-s which belong to some Order with complete field set to true, which, I believe, marks the Order as complete.
Then just show this result whenever you want to show it.

Moving Line Items from my Cart into an Order to checkout - Ruby on Rails

I followed this tutorial to build a shop with cart functionality. The tutorial ended there though and I am a bit woefully unprepared for this next step. If anyone could give me some direction on this, I would greatly appreciate it.
I currently have products I can add as line_items to a cart. I am unable to figure out how to take those line_items from the cart and pass them into an order. After the order is saved/placed, I want to destroy the cart and start again as the order is being processed.
My guess?: take the line items from the cart as an array/hash and add it to the order, but how?
Rails 6.0.2.2
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
rbenv 1.1.2-28-gc2cfbd1
Cart Model
class Order < ApplicationRecord
has_one :cart
# has_many :order_items, dependent: :destroy
# has_many :products, through: :order_items
# def add_cart(cart)
# current_cart = Cart.find(session[:cart_id])
# current_cart.line_items
# end
# def total_price
# line_items.to_a.sum { |item| item.total_price }
# end
end
CurrentCart Concern in Models
module CurrentCart
private
def set_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
#cart = Cart.create
session[:cart_id] = #cart.id
end
end
LineItem Model
class LineItem < ApplicationRecord
belongs_to :product
belongs_to :cart
def total_price
product.price * quantity
end
end
Order Model
class Order < ApplicationRecord
belongs_to :cart
# has_many :order_items, dependent: :destroy
# has_many :products, through: :order_items
# def add_cart(cart)
# current_cart = Cart.find(session[:cart_id])
# current_cart.line_items
# end
# def total_price
# line_items.to_a.sum { |item| item.total_price }
# end
end
Cart Controller
class CartsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
#before_action :authenticate_user!, except: [:index, :show]
before_action :set_cart, only: [:show, :edit, :update, :destroy]
# GET /carts
# GET /carts.json
def index
#carts = Cart.all
end
# GET /carts/1
# GET /carts/1.json
def show
end
# GET /carts/new
def new
#cart = Cart.new
end
# GET /carts/1/edit
def edit
end
# POST /carts
# POST /carts.json
def create
#cart = Cart.new(cart_params)
respond_to do |format|
if #cart.save
format.html { redirect_to #cart, notice: 'Cart was successfully created.' }
format.json { render :show, status: :created, location: #cart }
else
format.html { render :new }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /carts/1
# PATCH/PUT /carts/1.json
def update
respond_to do |format|
if #cart.update(cart_params)
format.html { redirect_to #cart, notice: 'Cart was successfully updated.' }
format.json { render :show, status: :ok, location: #cart }
else
format.html { render :edit }
format.json { render json: #cart.errors, status: :unprocessable_entity }
end
end
end
# DELETE /carts/1
# DELETE /carts/1.json
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
respond_to do |format|
format.html { redirect_to root_path, notice: 'Cart was successfully destroyed.' }
format.json { head :no_content }
end
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 root_path, notice: "That cart doesn't exist"
end
end
LineItem Controller
class LineItemsController < ApplicationController
include CurrentCart
before_action :set_line_item, only: [:show, :edit, :update, :destroy]
before_action :set_cart, only: [:create]
# GET /line_items
# GET /line_items.json
def index
#line_items = LineItem.all
end
# GET /line_items/1
# GET /line_items/1.json
def show
end
# GET /line_items/new
def new
#line_item = LineItem.new
end
# GET /line_items/1/edit
def edit
#line_item = LineItem.find(params[:id])
end
# POST /line_items
# POST /line_items.json
def create
product = Product.find(params[:product_id])
#line_item = #cart.add_product(product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart, notice: 'Item added to cart.' }
format.json { render :show, status: :created, location: #line_item }
else
format.html { render :new }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /line_items/1
# PATCH/PUT /line_items/1.json
def update
respond_to do |format|
if #line_item.update(line_item_params)
format.html { redirect_to #line_item, notice: 'Item was successfully updated.' }
format.json { render :show, status: :ok, location: #line_item }
else
format.html { render :edit }
format.json { render json: #line_item.errors, status: :unprocessable_entity }
end
end
end
# DELETE /line_items/1
# DELETE /line_items/1.json
def destroy
#cart = Cart.find(session[:cart_id])
#line_item.destroy
respond_to do |format|
format.html { redirect_to cart_path(#cart), notice: 'Item successfully removed.' }
format.json { head :no_content }
end
end
def add_quantity
#line_item = LineItem.find(params[:id])
#line_item.quantity += 1
#line_item.save
redirect_to root_url
end
def reduce_quantity
#line_item = LineItem.find(params[:id])
if #line_item.quantity > 1
#line_item.quantity -= 1
end
#line_item.save
redirect_to root_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_line_item
#line_item = LineItem.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def line_item_params
params.require(:line_item).permit(:product_id)
end
end
OrderController
class OrdersController < ApplicationController
before_action :set_cart, only: [:new, :create]
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
#orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
end
# GET /orders/new
def new
#order = Order.new
#order = #cart.line_items
end
# GET /orders/1/edit
def edit
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render :show, status: :created, location: #order }
else
format.html { render :new }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /orders/1
# PATCH/PUT /orders/1.json
def update
respond_to do |format|
if #order.update(order_params)
format.html { redirect_to #order, notice: 'Order was successfully updated.' }
format.json { render :show, status: :ok, location: #order }
else
format.html { render :edit }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
# DELETE /orders/1
# DELETE /orders/1.json
def destroy
#order.destroy
respond_to do |format|
format.html { redirect_to orders_url, notice: 'Order was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_order
#order = Order.find(params[:id])
end
# Only allow a list of trusted parameters through.
def order_params
params.fetch(:order, {})
end
end
Schema
ActiveRecord::Schema.define(version: 2020_04_22_162713) do
create_table "carts", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "line_items", force: :cascade do |t|
t.integer "product_id", null: false
t.integer "cart_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity", default: 1
t.index ["cart_id"], name: "index_line_items_on_cart_id"
t.index ["product_id"], name: "index_line_items_on_product_id"
end
create_table "orders", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "cart_id"
t.index ["cart_id"], name: "index_orders_on_cart_id"
end
create_table "products", force: :cascade do |t|
t.string "product_type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "length"
t.integer "width"
t.decimal "price", precision: 10, scale: 2
t.decimal "depth", precision: 3, scale: 3
end
create_table "themes", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "line_items", "carts"
add_foreign_key "line_items", "products"
add_foreign_key "orders", "carts"
end
Routes
Rails.application.routes.draw do
resources :orders
devise_for :users
resources :themes
resources :line_items
# resources :carts
resources :products
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
root "themes#index"
get 'carts/:id' => "carts#show", as: "cart"
delete 'carts/:id' => "carts#destroy"
post 'line_items/:id/add' => "line_items#add_quantity", as: "line_item_add"
post 'line_items/:id/reduce' => "line_items#reduce_quantity", as: "line_item_reduce"
post 'line_items' => "line_items#create"
delete 'line_items/:id' => "line_items#destroy"
end
I would recommend trying to seperate Cart and Order by creating another model similar to LineItem and call it something like OrderItem. This new model should have the same attributes of LineItem such as product_id and quantity. You will need to set up as similar belongs_to / has_many relationship here.
You need someway of determining what your current cart is through something like a current_cart helper stored on your session, I would recommend following this video for help on that -it will also take you away from scaffolding which may help you get more control over your code:
https://www.youtube.com/watch?v=rPmlA_T_J84
Equipped with current_cart you could in the OrdersController#create action copy line_items to order_items over like this:
def create
if #order = Order.save(order_params) # this will save the order
current_cart.line_items.each do |line_item|
# using the bang (!) to save to the DB and raise any errors
# rather than failing silently
#order.order_items.create!(
product_id: line_item.product_id,
quantity: line_item.quantity
)
end
redirect_to #order, notice: 'Order was successfully created.'
else
redirect_to root_path, notice: 'Something went wrong saving the order.'
end
end

Article/post drafts in Rails

I am working on a wiki application in Rails that would be publicly editable. I have an articles controller and a drafts controller. When someone clicks 'edit' on an article, I would like to create a new draft with the contents of the original article, and then save that to the database table when the user clicks 'save'. Any ideas on how I might go about doing this? I've been stuck on it for a few days.
Currently, each article belongs_to a category, a subcategory, and has_many drafts.
Database schema:
ActiveRecord::Schema.define(version: 20160723153357) do
create_table "articles", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "title"
t.text "content"
t.integer "category_id"
t.integer "subcategory_id"
end
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "drafts", force: :cascade do |t|
t.string "title"
t.text "content"
t.integer "category_id"
t.integer "subcategory_id"
t.integer "article_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subcategories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "category_id"
end
end
Articles_controller:
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
# GET /articles
# GET /articles.json
def index
if params[:category].blank? && params[:subcategory].blank?
#articles = Article.all.order("created_at DESC")
elsif params[:subcategory].blank?
#category_id = Category.find_by(name: params[:category]).id
#articles = Article.where(category_id: #category_id).order("created_at DESC")
else
#subcategory_id = Subcategory.find_by(name: params[:subcategory]).id
#articles = Article.where(subcategory_id: #subcategory_id).order("created_at DESC")
end
end
# GET /articles/1
# GET /articles/1.json
def show
end
# GET /articles/new
def new
#article = Article.new
end
# GET /articles/1/edit
def edit
end
# POST /articles
# POST /articles.json
def create
#parameters = article_params
#parameters[:category] = Category.find_by(id: Subcategory.find_by(id: article_params[:subcategory_id]).category_id)
#article = Article.new(#parameters)
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /articles/1
# PATCH/PUT /articles/1.json
def update
respond_to do |format|
if #article.update(article_params)
format.html { redirect_to #article, notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: #article }
else
format.html { render :edit }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
#article.destroy
respond_to do |format|
format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
#article = Article.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:title,:content,:subcategory_id)
end
end
Drafts_controller:
class DraftsController < ApplicationController
before_action :set_draft, only: [:show, :edit, :update, :destroy]
# GET /drafts
# GET /drafts.json
def index
#drafts = Draft.all
end
# GET /drafts/1
# GET /drafts/1.json
def show
end
# GET /drafts/new
def new
#draft = Draft.new
end
# GET /drafts/1/edit
def edit
end
# POST /drafts
# POST /drafts.json
def create
#parameters = draft_params
#parameters[:article_id] = params[:article_id]
#parameters[:subcategory_id] = 2
#parameters[:category_id] = 2
#draft = Draft.new(#parameters)
respond_to do |format|
if #draft.save
format.html { redirect_to #draft, notice: 'Draft was successfully created.' }
format.json { render :show, status: :created, location: #draft }
else
format.html { render :new }
format.json { render json: #draft.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /drafts/1
# PATCH/PUT /drafts/1.json
def update
respond_to do |format|
if #draft.update(draft_params)
format.html { redirect_to #draft, notice: 'Draft was successfully updated.' }
format.json { render :show, status: :ok, location: #draft }
else
format.html { render :edit }
format.json { render json: #draft.errors, status: :unprocessable_entity }
end
end
end
# DELETE /drafts/1
# DELETE /drafts/1.json
def destroy
#draft.destroy
respond_to do |format|
format.html { redirect_to drafts_url, notice: 'Draft was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_draft
#draft = Draft.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def draft_params
params.require(:draft).permit(:title, :content)
end
end
Article model:
class Article < ApplicationRecord
belongs_to :category
belongs_to :subcategory
has_many :drafts
end
Draft model:
class Draft < ApplicationRecord
belongs_to :category
belongs_to :subcategory
belongs_to :article
end
I think an approach I would probably follow would be to extract the content information to another table altogether. Here's a rough implementation I could come up with immediately:
class Article < ApplicationRecord
#column_names: ['type:string']
has_many :contents
has_one :current_content, -> { current.or(approved) }, class_name: 'Content'
delegate :title, :content, to: :current_content, allow_nil: true
end
class Content < ApplicationRecord
#column_names: ["article_id:int", "title:string", "content:text", "status:int"]
belongs_to :article
enum status: [:unapproved, :approved, :current]
end
class Draft < Article
#use STI here
end
#services/set_current_article_content.rb
class SetCurrentArticleContent
attr_reader :article, :content
def initialize(article, content)
#article = article
#content = content
end
def call
article.current_content.approved!
content.current!
end
end
#services/edit_wiki_content.rb
class EditWikiContent.rb
attr_reader :article, :content
def initialize(article, content)
#article = article
#content = content
end
def call
article.contents << content
content.save!
end
end
#services/publish_draft.rb
class PublishDraft
attr_reader :draft
def initialize(draft)
#draft = draft
end
def call
draft.becomes!(Article)
end
end
There are three services which would handle the updating and setting of the current content and also publishing the draft, you could add some additional logic in any of them. Also note the condition for the current_content in the article model, your logic might be different from the way I have implemented it.

Rails Association Bug

I am getting NoMethodError in SitesController#index undefined method `name' for nil:NilClass in my Salesman index view and cannot find the culprit.
I have a simple rails app with the following tables: Customer, Salesman and Invoice.
In the index view for the customer table I have the following call:
<% #customers.each do |customer| %>
<%= customer.name %></td>
<%= customer.address %>
<%= customer.salesman.name %>
<% end %>
This is the call that results in the undefined method ´name´ listed above. I made sure the salesman table has a salesman_id foreign key in the customer table. Also, I made the respective association in the models:
class Customer < ActiveRecord::Base
belongs_to :salesman
has_many :invoices
end
class Salesman < ActiveRecord::Base
has_many :customers
end
I tested the customer.salesman.name call in the console and it works flawlessly. The show view for the customer has an exact call like this one and it also works. The only way I can make the customer index pass is by replacing customer.salesman.name to customer.salesman_id returning only the id.
Thank you for your time here.
***schema.rb***
ActiveRecord::Schema.define(version: 20150325172212) do
create_table "customers", force: :cascade do |t|
t.string "name"
t.string "address"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "salesman_id"
end
create_table "invoices", force: :cascade do |t|
t.datetime "date"
t.string "fid"
t.integer "salesman_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "address"
end
create_table "salesmen", force: :cascade do |t|
t.datetime "name"
t.string "address""
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
***controllers***
**customer**
Class CustomersController < ApplicationController
before_action :set_customer, only: [:show, :edit, :update, :destroy]
# GET /customers
# GET /customers.json
def index
#customers = Customer.all
end
# GET /customers/1
# GET /customers/1.json
def show
end
# GET /customers/new
def new
#customer = Customer.new
end
# GET /customers/1/edit
def edit
end
# POST /customers
# POST /customers.json
def create
#customer = Customer.new(customer_params)
respond_to do |format|
if #customer.save
format.html { redirect_to #customer, notice: 'Customer was successfully created.' }
format.json { render :show, status: :created, location: #customer }
else
format.html { render :new }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /customers/1
# PATCH/PUT /customers/1.json
def update
respond_to do |format|
if #customer.update(customer_params)
format.html { redirect_to #customer, notice: 'Customer was successfully updated.' }
format.json { render :show, status: :ok, location: #customer }
else
format.html { render :edit }
format.json { render json: #customer.errors, status: :unprocessable_entity }
end
end
end
# DELETE /customers/1
# DELETE /customers/1.json
def destroy
#customer.destroy
respond_to do |format|
format.html { redirect_to customers_url, notice: 'Customer was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_cliente
#cliente = Cliente.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def customer_params
params.require(:customer).permit(:name, :address, :salesman_id)
end
end
***salesman***
class SalesmenController < ApplicationController
before_action :set_salesman, only: [:show, :edit, :update, :destroy]
# GET /salesmans
# GET /salesmans.json
def index
#salesmen = Salesman.all
end
# GET /salesmans/1
# GET /salesmans/1.json
def show
end
# GET /salesmans/new
def new
#salesman = Salesman.new
end
# GET /salesmans/1/edit
def edit
end
# POST /salesmans
# POST /salesmans.json
def create
#salesman = Salesman.new(salesman_params)
respond_to do |format|
if #salesman.save
format.html { redirect_to #salesman, notice: 'Salesman was successfully created.' }
format.json { render :show, status: :created, location: #salesman }
else
format.html { render :new }
format.json { render json: #salesman.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /salesmans/1
# PATCH/PUT /salesmans/1.json
def update
respond_to do |format|
if #salesman.update(salesman_params)
format.html { redirect_to #salesman, notice: 'Salesman was successfully updated.' }
format.json { render :show, status: :ok, location: #salesman }
else
format.html { render :edit }
format.json { render json: #salesman.errors, status: :unprocessable_entity }
end
end
end
# DELETE /salesmans/1
# DELETE /salesmans/1.json
def destroy
#salesman.destroy
respond_to do |format|
format.html { redirect_to salesmans_url, notice: 'Salesman was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_salesman
#salesman = Salesman.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def salesman_params
params.require(:salesman).permit(:name, :address)
end
**invoice**
class InvoicesController < ApplicationController
before_action :set_invoice, only: [:show, :edit, :update, :destroy]
# GET /invoices
# GET /invoices.json
def index
#invoices = Invoice.all
end
# GET /invoices/1
# GET /invoices/1.json
def show
#invoice = Invoice.find(params[:id])
#ordenes = #invoice.ordenes
end
# GET /invoices/new
def new
#invoice = Invoice.new
end
# GET /invoices/1/edit
def edit
end
# POST /invoices
# POST /invoices.json
def create
#invoice = Invoice.new(invoice_params)
respond_to do |format|
if #invoice.save
format.html { redirect_to #invoice, notice: 'Invoice was successfully created.' }
format.json { render :show, status: :created, location: #invoice }
else
format.html { render :new }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /invoices/1
# PATCH/PUT /invoices/1.json
def update
respond_to do |format|
if #invoice.update(invoice_params)
format.html { redirect_to #invoice, notice: 'Invoice was successfully updated.' }
format.json { render :show, status: :ok, location: #invoice }
else
format.html { render :edit }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# DELETE /invoices/1
# DELETE /invoices/1.json
def destroy
#invoice.destroy
respond_to do |format|
format.html { redirect_to invoices_url, notice: 'Invoice was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invoice
#invoice = Invoice.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def invoice_params
params.require(:invoice).permit(:date, :fid, :name, :address, :salesman_id)
end
end
-davefogo
Add the following to your views instead of what you currently have<%= customer.salesman.name if customer.salesman %>
This will ensure that you're not calling name when customer doesn't have a salesman.
Try this:
<% #customers.each do |customer| %>
<%= customer.name %>
<%= customer.address %>
<%= customer.salesman.try(:name) %>
<% end %>
Do this:
<% #customers.each do |customer| %>
<% Rails.logger.debug "\n\n#{#customers} has a nil customer in it\n\n" if customer.nil? %>
<% Rails.logger.debug "\n\nCustomer #{customer.id} has no salesman for [#{customer.salesman_id}]\n\n" if customer.salesman.nil? %>
<%= customer.name %>
<%= customer.address %>
<%= customer.salesman.name %>
<% end %>
...and then check your logs after hitting the index.

NoMethodError when generating new view

I'm working on making a simple app that has users which can have many playlists. I'm trying to render the New view of a playlist but I'm getting this error:
NoMethodError in PlaylistsController#new
undefined method `playlist' for nil:NilClass
def new
#playlist = #user.playlist.new
end
Here's some context:
EDIT: I uploaded the relevant parts of my code to a gist.github:
https://gist.github.com/izikperz/164eab76e64d375d9075
Playlist_controller.rb
class PlaylistsController < ApplicationController
before_action :set_playlist, only: [:show, :edit, :update, :destroy]
:set_user
# GET /playlists
# GET /playlists.json
def index
#playlists = Playlist.all
end
# GET /playlists/1
# GET /playlists/1.json
def show
end
# GET /playlists/new
def new
#playlist = #user.playlist.new
end
# GET /playlists/1/edit
def edit
end
# POST /playlists
# POST /playlists.json
def create
#playlist = #user.playlists.new(playlist_params)
respond_to do |format|
if #playlist.save
format.html { redirect_to #user.playlist, notice: 'Playlist was successfully created.' }
format.json { render :show, status: :created, location: #playlist }
else
format.html { render :new }
#format.json { render json: #playlist.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /playlists/1
# PATCH/PUT /playlists/1.json
def update
respond_to do |format|
if #playlist.update(playlist_params)
format.html { redirect_to #playlist, notice: 'Playlist was successfully updated.' }
format.json { render :show, status: :ok, location: #playlist }
else
format.html { render :edit }
format.json { render json: #playlist.errors, status: :unprocessable_entity }
end
end
end
# DELETE /playlists/1
# DELETE /playlists/1.json
def destroy
#playlist.destroy
respond_to do |format|
format.html { redirect_to playlists_url, notice: 'Playlist was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_user
#user = User.find_by(params[:user_id])
end
# Use callbacks to share common setup or constraints between actions.
def set_playlist
#playlist = Playlist.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def playlist_params
params.require(:playlist).permit(:user_id, :title, :img)
end
end
in my routes.rb I have:
resources :users do
resources :playlists
end
My user.rb model:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
has_secure_password
validates :password, length: { minimum: 6 }
has_many :playlists
end
Playlist.rb model:
class Playlist < ActiveRecord::Base
belongs_to :user, inverse_of: :playlist
validates :user_id, presence: true
end
My database schema:
ActiveRecord::Schema.define(version: 20141105043809) do
create_table "playlists", force: true do |t|
t.string "title"
t.string "img"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
create_table "users", primary_key: "user_id", force: true do |t|
t.string "name"
t.string "email"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password_digest"
t.string "imgurl"
end
end
Anyone have any ideas?
The #user object in the new action is nil.
undefined method `playlist' for nil:NilClass
This is because params[:user_id] was not sent from your view.
Fix it by chaniging:
new_user_playlist_path(params[:user_id])
to:
new_user_playlist_path(user_id: params[:user_id])
After fixing that, another error will arise in the new action:
#playlist = #user.playlist.new
It should be pluralized as it is in your association:
#playlist = #user.playlists.new
#or
#playlist = #user.playlists.build
Or Simply ignore that:
#playlist = Playlist.new(user_id: #user.id)
Since the ActiveRecord Association between User and Playlist is :has_many and :belongs_to these are many to many associations. This implies that one user has more than one playlists. The association will provide you with a instance method #user.playlists not #user.playlist. Try to use the plural version. It should work.
Make sure that you are sending user_id along with form so that
#user = User.find_by(id: params[:user_id]) is not nil
and then change this
#playlist = #user.playlists.new

Resources