ActiveRecord::InvalidForeignKey Error - ruby-on-rails

When I try to delete something from my inventory database, I get this error:
ActiveRecord::InvalidForeignKey in InventoriesController#destroy
SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "inventories" WHERE "inventories"."id" = ?
In the terminal, it says
{"_method"=>"delete", "authenticity_token"=>"dBNU2GkV0+rOcp4NVEljm4oIpkdOnPsvZKdmisaadBzX3QkY1VwurZNRPL0WFtVvizeAcJb7H6E50ObmpRsXAg==", "id"=>"1"}
It also says the source is at:
def destroy
#inventory = Inventory.find(params[:id])
#inventory.destroy
redirect_to inventory_path
end
In my inventories file which is:
class InventoriesController < ApplicationController
def show
#inventory = Inventory.find(params[:id])
end
def index
#inventories = Inventory.all
end
def new
#inventory = Inventory.new
end
def create
#inventory = Inventory.new(inventory_params)
if #inventory.save
redirect_to #inventory
else
render 'new'
end
end
def edit
#inventory = Inventory.find(params[:id])
end
def update
#inventory = Inventory.find(params[:id])
if #inventory.update(inventory_params)
redirect_to #inventory
else
render 'edit'
end
end
def destroy
#inventory = Inventory.find(params[:id])
#inventory.destroy
redirect_to inventory_path
end
end
private
def inventory_params
params.require(:inventory).permit(:product_name, :brand_name, :item_id, :upc_code, :color, :department, :size, :condition, :fabric_type, :shipping_weight, :sku, :asin, :quantity, :cost_price, :sell_price, :key_product_features, :product_description, :search_terms, :status, :listing_in_usa, :listing_in_canada, :listing_in_mexico)
end

It sounds like there is a foreign key to the inventories table from another one, which you would expect to see expressed in the Inventory model with a has_many or has_one relationship.
The fixes would be to either have the foreign key configured to automatically delete the child records, or to specify dependent: :destroy on the association.
The former would be very fast, but would not allow callbacks on the child instances to execute, so I'd recommend the :destroy option.

Related

How can I display a user's tasks which is related by the same tag?

I am creating a todo application via Ruby on Rails. I have created my own model for tags and taggings without the use of act_as_taggable gem. I have used devise for user authentication. One of my feature is to have a page which can show all of the user's tasks which is related by tags. However I have tried to change my show and index method in the tags controller to incorporate current_user but it always throws me this error
(undefined method tag for # Did you mean? tags tap):
Error
I am unable to figure out how I can edit my code to create tags within the current_user and properly process the tag_list for my current_user.
This is are the relevant codes:
Task model
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
validates :item, presence: true,
length: { minimum: 5 }
belongs_to :user
validate :due_date_cannot_be_in_the_past
def self.tagged_with(name)
Tag.find_by!(name: name).tasks
end
def tag_list
tags.map(&:name).join(" ")
end
def tag_list=(names)
self.tags = names.split(" ").map do |name|
Tag.where(name: name).first_or_create!
end
end
def due_date
due.to_s
end
def due_date=(str)
self.due = Chronic.parse(str).to_date.to_s
rescue
#invalid_date = true
end
def validate
errors.add :due, 'is not a valid date' if #invalid_date
end
def due_date_cannot_be_in_the_past
if due.past?
errors.add(:due_date, "is not a valid date")
end
end
end
Task Controller:
def index
#incomplete_tasks = Task.where(complete: false)
#complete_tasks = Task.where(complete: true)
end
def show
#task = current_user.tasks.find(params[:id])
end
def new
#task = current_user.tasks.build
end
def edit
#task = Task.find(params[:id])
end
def create
#task = current_user.tasks.build(task_params)
if #task.save
redirect_to #task
else
render 'new'
end
end
def update
#task = Task.find(params[:id])
if #task.update(task_params)
redirect_to #task
else
render 'edit'
end
end
def destroy
#task = Task.find(params[:id])
#task.destroy
redirect_to tasks_path
end
def complete
#task = current_user.tasks.find(params[:id])
#task.update_attribute(:complete, true)
flash[:notice] = "Task Marked as Complete"
redirect_to tasks_path
end
private
def task_params
params.require(:task).permit(:item, :description, :tag_list, :due)
end
end
Tags Controller:
def show
#tag = current_user.tag.find(params[:id])
end
def index
#tag = current_user.tag.all
end
Please do let me know if any other information is needed to make this problem clearer.

undefined method `build_profile' for #<User:0x000000035b9ae8>

I can't figure out the problem with my code. I'm trying to add a profile to my user. For this I get my user ID and attach this to my profile in a DB. However after submitting the form it gives the following error: NoMethodError in ProfilesController#create
class ProfilesController < ApplicationController
# GET to /users/:user_id/profile/new
def new
#profile = Profile.new
end
# POST to /users/:user_id/profile
def create
# Ensure that we have the user who is filling out form
#user = User.find(params[:user_id])
# Create profile linked to this specific user
#profile = #user.build_profile( profile_params )
if #profile.save
flash[:success] = "Profile updated!"
redirect_to root_path
else
render action: :new
end
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :phone_number, :contact_email, :banking)
end
end
Your models need to be some thing like this... I assume you are missing a has_many or belongs_to in the user modal.
class User
has_many :profile
# or belongs_to :profile
end
class Profile
belongs_to :user
# or has_many :users
end
Give your modals if it doesn't work, we can fix it up.
try to replace
#user = User.find(params[:user_id])
#profile = #user.build_profile( profile_params )
by
#profile.user_id = current_user.id
for user_id it depend how you named your user id foreign key
You may do
def create
#profile = Profile.new(profile_params)
if #profile.save
flash[:success] = "Profile updated!"
redirect_to root_path
else
render action: :new
end
end
private
def profile_params
params.require(:profile).permit(:first_name, :last_name, :phone_number, :contact_email, :banking, :user_id)
end
Or
def profile_params
params.require(:profile).permit(:first_name, :last_name, :phone_number, :contact_email, :banking).merge!(user: current_user)
end
Regarding the error "NoMethodError in ProfilesController#create" it may happen because it is not declared in the routes.rb or it does a HTTP Get instead of a Post.
in the routes.rb file,
resources :users do
resources :profiles
end
http://guides.rubyonrails.org/routing.html#nested-resources

Error getting user's name and email from the associated model in Ruby on Rails

I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url

How to restrict current_user from adding more than 3 order_items to a order per time period?

I'm building a store in Rails that has a specific sales model. I need to allow a user to add only 3 items to his order per 30 days. The 30 days counter should start upon adding the first order_item. Once 30 days expires, user would be able to add 3 orders. If 30 days didn't pass and for an example, user adds two order_items he would still be allowed to add one more order_item within 30 days.
So as well if user tries to add more then 3 items to show an error message and disregard saving of the order_items to current_user's order.
I have products, orders, order_items, users. I guess that I should add something to user model but I'm not sure what.
order_items_controller.rb
def create
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.user_id = current_user.id
#order.save
session[:order_id] = #order.id
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
end
private
def order_item_params
params.require(:order_item).permit(:quantity, :product_id, :user_id)
end
end
user.rb
class User < ActiveRecord::Base
has_many :identities, dependent: :destroy
has_many :order
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :omniauthable, :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable
end
order_item.rb
class OrderItem < ActiveRecord::Base
belongs_to :product
belongs_to :order
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :product_present
validate :order_present
before_save :finalize
def unit_price
if persisted?
self[:unit_price]
else
product.price
end
end
def total_price
unit_price * quantity
end
private
def product_present
if product.nil?
errors.add(:product, "is not valid or is not active.")
end
end
def order_present
if order.nil?
errors.add(:order, "is not a valid order.")
end
end
def finalize
self[:unit_price] = unit_price
self[:total_price] = quantity * self[:unit_price]
end
end
order.rb
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
private
def set_order_status
self.order_status_id = 1
end
def update_subtotal
self[:subtotal] = subtotal
end
end
carts_controller.rb
class CartsController < ApplicationController
def show
#order_items = current_order.order_items
end
routes.rb
resources :order_items, only: [:create, :update, :destroy, :new]
form.html.erb
<%= form_for OrderItem.new, html: {class: "add-to-cart"}, remote: true do |f| %>
<div class="input-group">
<%= f.hidden_field :quantity, value: 1, min: 1 %>
<div class="input-group-btn">
<%= f.hidden_field :product_id, value: product.id %>
<%= f.submit "Add to Cart", data: { confirm: 'Are you sure that you want to order this item for current month?'}, class: "btn btn-default black-background white" %>
</div>
</div>
<% end %>
</div>
I would add a begin_date and a order_counter to user model. Every time you add an order, look if the begin_date is more than 30 days ago, then set the begin_date to the actual date. If the begin_date is less than 30 days ago, increase the counter. And if the counter ist already 3 refuse the order.
You can add the columns to the user table by the command line argument
rails generate migration AddOrderCounterToUser
This will create a class in db/migrations:
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :users, :begin_date, :date
add_column :users, :order_counter, :integer
end
end
Add the additional attributes in your UserController to permit them in user_params.
Then change the create method in your OrderItemController
def create
now = Date.today
success = false
if current_user.begin_date && ((now - 30) < current_user.begin_date)
if current_user.order_counter >= 3
# deal with the case that order should not be created,
# for example redirect.
else
current_user.order_counter += 1
current_user.save
success = true
end
else
current_user.order_counter = 1
current_user.begin_date = now
current_user.save
success = true
end
if success
#order = current_order
#order_item = #order.order_items.new(order_item_params)
#order.user_id = current_user.id
#order.save
session[:order_id] = #order.id
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
else
respond_to do |format|
format.js { flash[:notice] = "CREATION NOT POSSIBLE." }
end
end
end
You can also put the checking code in a method in the user model, that would be cleaner.
Generally, when you don't want to create an element in rails under certain circumstances, you should choose to handle the situation via validators.
You could take a nesting approaches here:
Nest your OrderItem routes under Order (you can find further information about nesting in the Rails Guides about Nested Routing)
You should start by adding a new database column first_item_added_at to you Order model
rails generate migration AddFirstItemAddedAtToOrder
class AddFirstItemAddedAtToOrder < ActiveRecord::Migration
def change
add_column :orders, :first_item_added_at, :date
end
end
When nesting, you would create a new OrderItem via the route
POST /orders/:id/order_items
Then, you have to add a validator to your OrderItem model
class OrderItem < ActiveRecord::Base
validate :only_3_items_in_30_days
private
def only_3_items_in_30_days
now = Date.new
days_since_first = now - order.first_item_added_at
if order.order_items.count > 2 && days_since_first < 30
errors.add(:base, 'only 3 items in 30 days are allowed')
end
true # this is to make sure the validation chain is not broken in case the check fails
end
end
Now your controller only needs to create a new item and save it
def create
#item = OrderItem.new(item_params)
if #item.save
render <whatever_you_want_to_render>
else
# #item will contain the errors set in the model's validator
render <error_reaction>
end
end
private
def item_params
params.require(:order_item).permit(
:attribute_1,
:attribute_2,
:order_id # << this one is very important
)
end
If you don't wish to nest OrderItem, than the model still remains the same, but your controller would look like:
def create
#item = OrderItem.new(order_item_params)
session[:order_id] = current_order.id
if #item.save
respond_to do |format|
format.js { flash[:notice] = "ORDER HAS BEEN CREATED." }
end
else
render <handling for error>
end
end
private
def order_item_params
base_params = params.require(:order_item)
.permit(:quantity, :product_id, :user_id)
base_params.merge(order: current_order)
end
Please note, that I added current_order.id to your order_item_params method.
EDIT: replaced order_id: current_order.id by order: current_order to provide the relation to the new OrderItem before it is actually saved

Rails 4: Undefined method `total_price' for nil:NilClass, Order Controller

I'm having trouble having order go through. I have posted the error bellow. I think the issue has to do with the create method in the OrderController.rb, I do have the total_price method already defined but.. other than that I'm not sure how to fix the issue. Any help would be appreciated. Thank you.
class OrderTransaction
def initialize order, nonce
#order = order
#nonce = nonce
end
def execute
#result = Braintree::Transaction.sale(
amount: order.total_price,
payment_method_nonce: nonce
)
end
def ok?
#result.success?
end
private
attr_reader :order, :nonce
end
class Order < ActiveRecord::Base
belongs_to :user
has_many :order_items
def total_price
order_items.inject(0) { |sum, item| sum + item.total_price }
end
end
class OrdersController < ApplicationController
before_filter :initialize_cart
def index
#orders = Order.order(created_at: :desc).all
end
def create
#order_form = OrderForm.new(
user: User.new(order_params[:user]),
cart: #cart
)
if #order_form.save
notify_user
if charge_user
redirect_to root_path, notice: "Thank you for placing the order."
else
flash[:warning] = <<EOF
Your order ID is #{#order_form.order.id}.
<br/>
Something went wrong.
EOF
redirect_to new_payment_order_path(#order_form.order)
end
else
render "carts/checkout"
end
end
def update
#order = Order.find params[:id]
#previous_state = #order.state
if #order.update state_order_params
notify_user_about_state
redirect_to orders_path, notice: "Order was updated."
end
end
def new_payment
#order = Order.find params[:id]
#client_token = Braintree::ClientToken.generate
end
def pay
#order = Order.find params[:id]
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
transaction.execute
if transaction.ok?
redirect_to root_path, notice: "Thank you for placing the order."
else
render "orders/new_payment"
end
end
private
def notify_user
#order_form.user.send_reset_password_instructions
OrderMailer.order_confirmation(#order_form.order).deliver
end
def notify_user_about_state
OrderMailer.state_changed(#order, #previous_state).deliver
end
def order_params
params.require(:order_form).permit(
user: [ :name, :phone, :address, :city, :country, :postal_code, :email ]
)
end
def charge_user
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
transaction.execute
transaction.ok?
end
def state_order_params
params.require(:order).permit(:state)
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def total_price
self.quantity * self.product.price
end
end
class OrderForm
include ActiveModel::Model
attr_accessor :user, :order # credit_card
attr_writer :cart
def save
set_password_for_user
if valid?
persist
true
else
false
end
end
def has_errors?
user.errors.any?
end
private
def valid?
user.valid?
end
def persist
user.save
#order = Order.create! user: user
build_order_items
end
def set_password_for_user
user.password = Digest::SHA1.hexdigest(user.email + Time.now.to_s)[0..8]
end
def build_order_items
#cart.items.each do |item|
#order.order_items.create! product_id: item.product_id, quantity: item.quantity
end
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def total_price
self.quantity * self.product.price
end
end
As a standard note, any NilClass error basically means you haven't defined the variable you're trying to manipulate.
The key to solving the problem is to therefore find why the variable isn't defined, and populate it.
def execute
#result = Braintree::Transaction.sale(
amount: order.total_price,
payment_method_nonce: nonce
)
end
This is where Rails says the variable is not populated.
However, as with many problems in programming, the cause of the issue may not be as defined...
I initially thought the problem was that you weren't calling #order. However, the class initializes with order, so that shouldn't be a problem. So you have to look at how you're invoking the class:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
This surmises that #order is defined.
I surmise it isn't.
Here's what I'd do:
def create
#order_form = OrderForm.new(
user: User.new(order_params[:user]),
cart: #cart
)
if #order_form.save
notify_user
#order = #order_form.order #-> not efficient but should create #order
if charge_user
redirect_to root_path, notice: "Thank you for placing the order."
else
flash[:warning] = <<EOF
Your order ID is #{#order_form.order.id}.
<br/>
Something went wrong.
EOF
redirect_to new_payment_order_path(#order_form.order)
end
else
render "carts/checkout"
end
end
Personally, I think this highlights a deeper problem with your code structure:
You're creating an OrderForm object and yet processing #order_form.order
Your controller is full of tiny methods which bloat it up big time
Your controller is for orders, yet builds OrderForm objects
I'd do my best to make my controller as thin as possible:
#app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def new
#order = current_user.order.new
end
def create
#order = current_user.order.new order_params
if #order.save
#order.charge
end
end
private
def order_params
params.require(:order).permit(:x, :y, :z, order_products_attributes: [:product, :qty])
end
end
I'd have a more modular model structure:
#app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
has_many :order_products
has_many :products, through: :order_products, extend ProductQty
has_many :payments, inverse_of: :order
scope :cart, -> { order_products }
def total_price
products.pluck(:price, :qty) #-> need to work out
end
def charge
payment = payments.create
payment.execute ? payment.success : payment.error #-> something conditional
end
end
#app/models/order_product.rb
class OrderProduct < ActiveRecord::Base
#columns id | order_id | product_id | qty | created_at | updated_at
belongs_to :order
belongs_to :product
end
#app/models/payment.rb
class Payment < ActiveRecord::Base
belongs_to :order, inverse_of: :payments
def execute
Braintree::Transaction.sale(amount: order.total_price)
end
end
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :order_products
has_many :orders, through: :order_products
end
#app/models/concerns/product_qty.rb
module ProductQty
#Load
def load
products.each do |qty|
proxy_association.target << qty
end
end
#Private
private
#Products
def products
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({qty: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:qty)
end
#Target
def target_collection
proxy_association.target
end
end
I wanted to include cart somewhere, I'll have to do that another time.
For now, you'd be able to do the following:
#order = current_user.orders.find params[:id]
#order.products.each do |product|
product.qty #-> 5
#order.total_price #-> prices * qtys
--
This is not complete or tested, but I hope it shows you how you could improve your code structure dramatically, by making it modular. IE keep as many methods tied to your objects as possible.
In short, you should be able to do the following:
#order = current_users.orders.find params[:id]
if #order.payments.any?
#payment = #order.payment.first
#payment.success?
end
The problem is in your charge_user method inside OrdersController class where you call this code:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
you don't really defined #order in this method, i.e. #order is nil here and that's causing the problem for you here and you are getting this error: undefined method total_price for nil:NilClass
Set #order value inside the charge_user method before you call this line of code and make sure #order is NOT nil:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
One possible solution is to modify your charge_user method to take an order argument like this:
def charge_user(order)
transaction = OrderTransaction.new order, params[:payment_method_nonce]
transaction.execute
transaction.ok?
end
And, in your create method call like this:
if charge_user(#order_form.order)
redirect_to root_path, notice: "Thank you for placing the order."
else
# rest of the code
end
This will solve your issue.

Resources