My scenario:
I have SaleItem model, which stores (:sale_id, :product_id, :quantity, :total_price).
class SaleItem < ApplicationRecord
belongs_to :sale
belongs_to :product
before_save :set_unit_price
before_save :set_total_price
before_save :set_total_disscounted_price
def unit_price
if persisted?
self[:unit_price]
else
product.price_pence
end
end
def total_price
unit_price * quantity
end
private
def set_unit_price
self[:unit_price] = unit_price
end
def set_total_price
self[:total_price] = quantity * set_unit_price
end
def set_total_disscounted_price
self[:total_disscounted_price] =self[:total_price] - (self[:total_price] * self.sale.event.event_disscount) / 100
end
end
My problem is, whenever I create new SaleItem object I want to check if the same record already exists and if it does then I just need to add up the :quantity and recalculate total price (in the model) according to the new :quantity.
What I am confused about is is it possible to check it within the create method?
So far I found this rails method first_or_create.
This is my initial code from SaleItem controller
class SaleItemsController < ApplicationController
def create
#sale = #sale_item.sale
#sale_item = SaleItem.create(sale_item_params)
#event = #sale.event
if #sale_item.save
redirect_to event_sale_path(#event, #sale)
else
redirect_to event_sale_path(#event, #sale)
end
end
private
def sale_item_params
params.require(:sale_item).permit(:sale_id, :product_id, :quantity)
end
end
and then after I found first_or_create I started changing it to this, but havent finished as I am a bit stuck:
class SaleItemsController < ApplicationController
def create
#sale_item = SaleItem.where(sale_id: params[:sale_id], product_id: sale_item_params[:product_id]).first_or_create do |sale_item|
sale_item.quantity = sale_item.quantity.to_i + sale_item_params[:quantity].to_i
end
#sale = #sale_item.sale
#sale_item = SaleItem.create(sale_item_params)
#event = #sale.event
if #sale_item.save
redirect_to event_sale_path(#event, #sale)
else
redirect_to event_sale_path(#event, #sale)
end
end
private
def sale_item_params
params.require(:sale_item).permit(:sale_id, :product_id, :quantity)
end
end
I couldn't figure out how to update record that was found and dont change newly created record, so I created a variable #new_record and set it initially to false, and then once new record created, #new_record changes to true. In this way I was being able to track each record and change it if needed.
This is the SaleItem Controller updated code
class SaleItemsController < ApplicationController
def create
#new_record = false
#sale_item = SaleItem.where(sale_id: params[:sale_id], product_id: sale_item_params[:product_id]).first_or_create do |sale_item|
sale_item.quantity = sale_item_params[:quantity]
#new_record = true
end
#new_record == false ? #sale_item.quantity = #sale_item.quantity.to_i + sale_item_params[:quantity].to_i : sale_item_params[:quantity].to_i
#sale = #sale_item.sale
#event = #sale.event
if #sale_item.save
redirect_to event_sale_path(#event, #sale)
else
redirect_to event_sale_path(#event, #sale)
end
end
def destroy
#sale_item = SaleItem.find(params[:id])
#sale = #sale_item.sale
#event = #sale.event
#sale_item.destroy
redirect_to event_sale_path(#event, #sale)
end
private
def sale_item_params
params.require(:sale_item).permit(:sale_id, :product_id, :quantity)
end
end
Change your create action code from this
def create
#sale_item = SaleItem.where(sale_id: params[:sale_id], product_id: sale_item_params[:product_id]).first_or_create do |sale_item|
sale_item.quantity = sale_item.quantity.to_i + sale_item_params[:quantity].to_i
end
#sale = #sale_item.sale
#sale_item = SaleItem.create(sale_item_params)
#event = #sale.event
if #sale_item.save
redirect_to event_sale_path(#event, #sale)
else
redirect_to event_sale_path(#event, #sale)
end
end
to this
def create
#sale_item = SaleItem.find_or_create_by(sale_item_params) do |sale_item|
sale_item.quantity = sale_item.quantity.to_i + sale_item_params[:quantity].to_i
sale_item.save
end
#sale = #sale_item.sale
#event = #sale.event
if #sale_item.save
redirect_to event_sale_path(#event, #sale)
else
redirect_to event_sale_path(#event, #sale)
end
end
find_or_create_by(sale_item_params) will first find the sale_item with sale_id, product_id and quantity and then if there isn't any sale_item record found it will create a new one, In both case you can update the quantity.
I think you have to define the unique key to find SaleItem
for example, if sale_id and product_id are keys to find SaleItem.
it's better to have new strong parameter method for find or create SaleItem on create method.
controller example:
class SaleItemsController < ApplicationController
before_action :initialize_sale_item, only: [:create]
def create
#sale_item.assign_attributes(sale_item_params)
#sale_item.save
# render or respond_to ...
end
private
def sale_item_params
params.require(:sale_item).permit(:sale_id, :product_id, :quantity)
end
def sale_item_key_params
params.require(:sale_item).permit(:sale_id, :product_id)
end
def initialize_sale_item
#sale_item = SaleItem.find_or_initialize_by(sale_item_key_params)
end
end
as Talha Junaid suggested, you can use find_or_create_by instead of using find_or_initialize_by(and remove initialize_sale_item ...)
references
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_initialize_by
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by
My guess it you probably want to look at https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by
Related
I am having a problem when I add items into my cart where instead of updating an item if it already exists, it creates a duplicate item. I know I need to pass a validation to check if the order_item exists but I'm not quite sure how or where to do it.
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
helper_method :current_order
helper_method :current_buylist_order
def current_order
if !session[:order_id].nil?
Order.find(session[:order_id])
else
Order.new
end
end
end
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, :card_id)
end
end
class CartsController < ApplicationController
def show
#order_items = current_order.order_items
end
end
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
class OrderItem < ActiveRecord::Base
belongs_to :card
belongs_to :order
validates :quantity, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :card_present
validate :order_present
before_save :finalize
def unit_price
if persisted?
self[:unit_price]
else
card.price
end
end
def total_price
unit_price * quantity
end
private
def card_present
if card.nil?
errors.add(:card, "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
In you OrderItemsController, in create and update methods you are using line #order_item = #order.order_items.new(order_item_params) which initializes new item every time one of these methods is called. You need to change these methods, so they create new item only if item with same params doesn't already exist. Take a look at first_or_initialize or first_or_create method. Or use a simple condition (Eg if item exists in the order increase quantity by one, else create new item)
In your Order Items Controller, under the create method, have a code that checks for an existing product then increments the quantity if you add to cart more than once. You can use jQuery to disable the button too.
Under the create method in order_items_controller
#product_id = params['order_item']['product_id']
#order.order_items.each do |oi|
if oi.product_id.to_s == #product_id
updater(oi.id,params['order_item']['quantity']) and return
end
end
Add and extra method for the update in quantity
def updater(id,quantity)
#order = current_order
#order_item = #order.order_items.find(id)
quantity = #order_item.quantity + quantity.to_i
#order_item.update(quantity:quantity)
#order_items = #order.order_items
end
What worked for me is this:
def create
#order = current_order
if OrderItem.where("card_id = ? AND order_id = ?",
params[:order_item][:card_id], #order.id.to_s).empty?
#order_item = OrderItem.new(order_item_params)
#order_item.order = current_order
#order_item.save
else
#order_item = OrderItem.where("card_id = ? AND order_id = ?",
params[:order_item][:card_id], #order.id.to_s).first
quantity = #order_item.quantity += params[:order_item][:quantity].to_i
#order_item.update(quantity: quantity)
end
end
I'm getting a bit confused as to why I am receiving this error as (i think) it is suggesting that my current_customer returns nil. I do not know how to fix this, is it a problem with my current_customer method, sessions controller, or my events_controller...
Event Model:
class Event < ActiveRecord::Base
belongs_to :calendar
end
Events Controller:
class EventsController < ApplicationController
def new
#event = Event.new
#calendar = current_customer.calendar #WHY IS IT NOT ERRORING HERE TOO?
end
def create
**#calendar = current_customer.calendar #IT IS CALLING ERROR HERE**
#event = #calendar.build.event(event_params)
if #event.save
redirect_to '/main'
else
redirect_to '/compose'
end
end
private
def event_params
params.require(:event).permit(:calendar_id, :name, :starts_at, :ends_at)
end
Customer model:
class Customer < ActiveRecord::Base
belongs_to :business
has_one :calendar
has_secure_password
attr_accessor :remember_token
#remembers a user in the database for use in persistent sessions
def remember
self.remember_token = Customer.new_token
update_attribute(:remember_digest, Customer.digest(remember_token))
end
def Customer.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def forget
update_attribute(:remember_digest, nil)
end
def Customer.new_token
SecureRandom.urlsafe_base64
end
#returns true if the given token matches the digest
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
Customer controller:
class CustomersController < ApplicationController
def new
#customer = Customer.new
#businesses = Business.all
#calendar = Calendar.new
end
def create
#customer = Customer.create(customer_params)
#calendar = #customer.build_calendar
#customer.save!
session[:customer_id] = #customer.id
redirect_to '/'
rescue ActiveRecord::RecordInvalid => ex
render action: 'new', alert: ex.message
end
private
def customer_params
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password, :business_id)
end
end
Sessions controller:
class SessionsController < ApplicationController
def new
end
def create
#customer = Customer.find_by_email(params[:session][:email])
if #customer && #customer.authenticate(params[:session][:password])
session[:customer_id] = #customer.id #log in
#customer.remember
cookies.permanent.signed[:customer_id] = #customer.id
cookies.permanent[:remember_token] = #customer.remember_token
redirect_to '/main'
else
#need to add flash error: invalid password/email combination
redirect_to '/login'
end
end
def destroy
#current_customer.forget
cookies.delete(:customer_id)
cookies.delete(:remember_token)
session.delete(:customer_id)
#current_customer = nil
redirect_to '/'
end
end
Current_customer method within application.controller:
helper_method :current_customer
def current_customer
if (customer_id = session[:customer_id])
#current_customer ||= Customer.find_by(id: customer_id)
elsif (customer_id = cookies.signed[:customer_id])
customer = Customer.find_by(id: customer_id)
if customer && customer.authenticated?(cookies[:remember_token])
session[:customer_id] = customer.id #log in
#current_customer = customer
end
end
end
Calendar model:
class Calendar < ActiveRecord::Base
belongs_to :customer
has_many :events
end
calendars_controller:
class CalendarsController < ApplicationController
def new
#calendar = Calendar.new(calendar_params)
end
def create
#calendar = Calendar.new(calendar_params)
end
private
def calendar_params
params.require(:customer_id)
end
end
I am new to Ruby/Rails so I would really appreciate any explanations and answers, please help! thanks
#calendar = current_customer.calendar
#event = #calendar.build.event(event_params)
According to the error message, #calendar is nil, so when you call #calendar.build.event(event_params) in the next line, it calls: nil.build and hence you get the error.
For some reason, your current_customer's calendar is nil for that request. Figure that out and make sure the current_customer has a calendar present in the database, then it should work.
Also, you need to change:
#event = #calendar.build.event(event_params)
To:
#event = #calendar.events.build(event_params)
It would seem that #calendar is nil in the events controller.
Start by checking the current_customer passes back the customer you were expecting.
Then check that the current_customer has a calendar created for you to reference.
i've been having issues trying to build a Cart + Cart/OrderItem + Order combination simple enough yet effective. I've looked around online but i couldn't find anything that fit, so i tried something but .. i'm a bit blocked atm, i don't see how to continue with this. The problem is that i don't know how to get the items in the order and start the cart from scratch (btw it's kind of messy). Also a nice simple tutorial on this would be appreciated as well. Do note i already went through agile web's book example, but for some reason i didn't follow it, it didn't seem to be what i was looking for.
controllers - cart + order
class CartsController < ApplicationController before_filter :initialize_cart
def add
#cart.add_item params[:id]
session["cart"] = #cart.serialize
product = Product.find(params[:id])
redirect_to :back, notice: "Added #{product.name} to cart." end
def show end
def checkout
#order = Order.new user: User.new end end
class OrdersController < ApplicationController
def index
#orders = Order.order('created_at desc').page(params[:page])
end
def show
end
def new
#order = Order.new
end
def create
#order = Order.new(order_params)
respond_to do |format|
if #order.save
format.html { redirect_to root_path, notice:
'Thank you for your order' }
format.json { render action: 'show', status: :created,
location: #order }
else
format.html { render action: 'new' }
format.json { render json: #order.errors,
status: :unprocessable_entity }
end
end
end
private
def set_order
#order = Order.find(params[:id])
end
def order_params
params.require(:order).permit(:pay_type, :user_id)
end
end
now the models
class Order < ActiveRecord::Base
belongs_to :user
PAYMENT_TYPES = [ "Check", "Credit card", "Purchase order" ]
validates :pay_type, inclusion: PAYMENT_TYPES
end
class CartItem
attr_reader :product_id, :quantity
def initialize product_id, quantity = 1
#product_id = product_id
#quantity = quantity
end
def product
Product.find product_id
end
def total_price
product.price * quantity
end
end
class Cart
attr_reader :items
def self.build_from_hash hash
items = if hash["cart"] then
hash["cart"]["items"].map do |item_data|
CartItem.new item_data["product_id"], item_data["quantity"]
end
else
[]
end
new items
end
def initialize items = []
#items = items
end
def add_item product_id
item = #items.find { |item| item.product_id == product_id }
if item
item+=1
else
#items << CartItem.new(product_id)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def serialize
items = #items.map do |item|
{
"product_id" => item.product_id,
"quantity" => item.quantity
}
end
{
"items" => items
}
end
def total_price
#items.inject(0) { |sum, item| sum + item.total_price }
end
end
Thank you.
I have an association between experience and company where company has_many :experiences and experience belongs_to :company, Now I want to design this: if a user enters a company name that already exist in the company model it will assign to it but if the user put a company name that doesn't already exist in company model - the company must persist but not be created as a new record (I don't want to use find_or_create method and the find_by doesn't seem working for this situation)
Edit
this is my Company controller
def allcompanies
#companies = Company.paginate(page: params[:page], per_page: 10).order("created_at DESC")
end
def show
#company = Company.find_by_slug(params[:id])
if #company
render action: :show
else
render file: 'public/404', status: 404, formats: [:html]
end
end
def index
#companies = Company.limit(19).order("id DESC")
end
def new
#company = Company.new
end
def edit
end
def create
#company = Company.new(company_params)
respond_to do |format|
if #company.save
flash[:notice] = "Entreprise '#{#company.name}' Crée!"
format.html { redirect_to #company}
else
format.html { render action: 'new' }
end
end
end
def update
respond_to do |format|
if #company.update(company_params)
flash[:notice] = "Entreprise '#{#company.name}' à été mis à jour"
format.html { redirect_to #company }
else
format.html { render action: 'edit' }
end
end
end
def destroy
#company.destroy
respond_to do |format|
format.html { redirect_to companies_url }
end
end
private
def set_company
#company = Company.find_by_slug(params[:id])
end
def company_params
params.require(:company).permit(:name, :company_description, :country, :city, :company_status, :company_size, :company_website, :sector_ids, :job_id, :companylogo)
end
and this is my experience model that actually create a new record for each company
belongs_to :profile
belongs_to :company
validates :company_name, presence: true
def company_name
company.try(:name)
end
def company_name=(name)
self.company = Company.find_or_initialize_by(name: name)
end
OK, so you want some companies to be visible and some not.
Then, you can create a corresponding boolean field, like public_visible and set it to false for companies you don't know(ie created by application users).
In your controller do something like this:
#company = Company.find_by_name(params[:company][:name])
if #company.blank?
#company = Company.create(params[:company].merge({ public_visible: false }))
end
#company.experiences.create(...)
it is just an example, since I don't know your real conditions on companies creation, so you need to adjust it according to your needs.
I have the following controllers:
1- students_controller.rb
class StudentsController < ApplicationController
def index
#students = Student.all
end
def show
#student = Student.find(params[:id])
end
def new
#student = Student.new
end
def create
#student = Student.new(params[:student])
if #student.save
flash[:notice] = ' Student Record Saved Successfully. Please fill the Parent Details.'
redirect_to new_student_guardian_path(#student.id)
else
flash[:error] = 'An error occurred please try again!'
render 'new'
end
end
def edit
end
end
2- guardians_controller.rb
class GuardiansController < ApplicationController
before_filter :set_student, only: [:new, :create]
def index
#guardian = Guardian.all
end
def show
#guardian = Guardian.find(params[:id])
end
def new
#guardian = Guardian.new
end
def create
#guardian = Guardian.new(params[:guardian])
if #guardian.save
flash[:notice] = ' Parent Record Saved Successfully. Please fill the Additional Details.'
redirect_to new_student_previous_detail_path(#student)
else
flash.now[:error] = 'An error occurred please try again!'
render 'new'
end
end
def edit
end
private
def set_student
#student = Student.find(params[:student_id])
end
end
3- previous_details_controller.rb
class PreviousDetailsController < ApplicationController
before_filter :set_student, only: [:new, :create]
def index
#previous_detail = PreviousDetail.all
end
def show
#previous_detail = PreviousDetail.find(params[:id])
end
def new
#previous_detail = PreviousDetail.new
end
def create
#previous_detail = PreviousDetail.new(params[:previous_detail])
if #previous_detail.save
flash[:notice] = 'Record Saved Successfully.'
# redirect_to user profile page
else
flash.now[:error] = 'An error occurred please try again!'
redirect_to '/student/admission1'
end
end
def edit
end
private
def set_student
#student = Student.find(params[:student_id])
end
end
4- student.rb
class Student < ActiveRecord::Base
after_create :add_to_users
belongs_to :user
accepts_nested_attributes_for :guardians
has_one :previous_detail
accepts_nested_attributes_for :previous_detail
def add_to_users
new_user = User.new
new_user.user_name = self.first_name
new_user.first_name = self.first_name
new_user.last_name = self.last_name
new_user.email = self.email
new_user.password = "123456"
new_user.password_confirmation = "123456"
new_user.user_type_id = 3
new_user.save
end
end
This callback is used to create the student in users model.
How can i tell the previous_details_controller.rb to go to the user profile page(the student who just created and also created in users model using the after create :add_to_user) ?
I have solved my problem and I will add the slolution maybe will be helpful for anyone if needed.
1- Modifying the callback method add_to_user to the following
def add_to_users
new_user = User.new
new_user.user_name = self.first_name
new_user.first_name = self.first_name
new_user.last_name = self.last_name
new_user.email = self.email
new_user.password = "123456"
new_user.password_confirmation = "123456"
new_user.user_type_id = 3
self.user_id = new_user.id
new_user.save
t = Student.find(self.id)
t.user_id = new_user.id
t.save
end
so it will add the student to users model and also will update the user_id in student model to make a match between both.
also i have modified the name of callback above to after_create :add_to_users , on: :create to make sure it will raise on create action only.
2- Modifying the create action in previous_details_controller where i need to redirect to user profile to the following
def create
#previous_detail = PreviousDetail.new(params[:previous_detail])
if #previous_detail.save
flash[:notice] = 'Record Saved Successfully.'
redirect_to user_path(#student.user_id)
else
flash.now[:error] = 'An error occurred please try again!'
redirect_to '/student/admission1'
end
end
after typing all this will redirect me to the User's profile page which belongs to the student who newly added to the system.