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.
Related
I'm using paypal-recurring gem for my app and i can't figure out how to cancel a user's subscription. i tried gabriel's answer from a similar question on here, but i get a NoMethodError in SubscriptionsController#suspend undefined method 'suspend_paypal' for User` when trying to cancel the subscription.
In my subscriptions.rb model
class Subscription < ApplicationRecord
belongs_to :plan
belongs_to :user
validates_presence_of :plan_id
validates_presence_of :user_id
attr_accessor :paypal_payment_token
def save_with_payment
if valid?
if paypal_payment_token.present?
save_with_paypal_payment
end
end
end
def paypal
PaypalPayment.new(self)
end
def save_with_paypal_payment
response = paypal.make_recurring
self.paypal_recurring_profile_token = response.profile_id
save!
end
def suspend_paypal
paypal.suspend
self.status = "canceled"
save
end
def payment_provided?
paypal_payment_token.present?
end
end
subscriptions_controller
class SubscriptionsController < ApplicationController
before_action :authenticate_user! , only: [:new, :edit]
def new
plan = Plan.find(params[:plan_id])
#subscription = plan.subscriptions.build
#subscription.user_id = current_user.id
if params[:PayerID]
#subscription.paypal_cutomer_token = params[:PayerID]
#subscription.paypal_payment_token = params[:token]
#subscription.email = #subscription.paypal.checkout_details.email
end
end
def index
end
def create
#user = current_user
#subscription = Subscription.new(subscription_params)
if #subscription.save_with_payment
redirect_to root_path, :notice => "Thank you for subscribing"
#user.premium!
else
render :new
end
end
def suspend
#user = current_user
#user.suspend_paypal
flash[:success] = 'Subscription Canceled.'
#user.free!
redirect_to root_path
end
def show
#subscription = Subscription.find(params[:id])
end
def paypal_checkout
plan = Plan.find(params[:plan_id])
subscription = plan.subscriptions.build
redirect_to subscription.paypal.checkout_url(
return_url: new_subscription_url(:plan_id => plan.id),
cancel_url: root_url,
)
end
private
def subscription_params
params.require(:subscription).permit!
end
end
paypal_payment.rb
class PaypalPayment
def initialize(subscription)
#subscription = subscription
end
def checkout_details
process :checkout_details
end
def payment_active?
self.status = "active"
end
def suspend
process :suspend, :profile_id => #subscription.paypal_recurring_profile_token
end
def checkout_url(options)
process(:checkout, options).checkout_url
end
def save_with_paypal_payment
response = paypal.make_recurring
self.paypal_recurring_profile_token = response.profile_id
save!
end
def make_recurring
process :request_payment
process :create_recurring_profile, period: :monthly, frequency: 1, start_at: Time.zone.now
end
private
def process(action, options = {})
options = options.reverse_merge(
token: #subscription.paypal_payment_token,
payer_id: #subscription.paypal_cutomer_token,
description: #subscription.plan.name,
amount: #subscription.plan.price,
currency: "USD"
)
response = PayPal::Recurring.new(options).send(action)
raise response.errors.inspect if response.errors.present?
response
end
end
View
<%= link_to "Suspend paypal", subscriptions_suspend_path, :data => { :confirm => "Are you sure?" } %>
You have defined suspend_paypal on the Subscription model, but you're trying to call it on user. Get the subscription instead and call the method:
# class SubscriptionsController
def suspend
#subscription = Subscription.find(params[:id])
#subscription.suspend_paypal
flash[:success] = 'Subscription Canceled.'
current_user.free!
redirect_to root_path
end
Note -- you should add authorization logic, otherwise any user who can authenticate can suspend an arbitrary subscription by sending its ID. If a user has_many subscriptions, subscription = current_user.subscriptions.find(params[:id]). If has_one, just current_user.subscription and validate that that matches params[:id] for good measure.
You probably also don't need subscription to be an ivar since you're simply redirecting to root. Just replace #subscription with subscription. Use ivars when you need to access those variables in the views.
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
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 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.
I upgraded to rails 4 and now I am no longer able to register users on my app. It seems like my gallery (carrierewave) has broken down. I have inspected the code and can't notice anything that would stop it from working now. I get a undefined method `galleries' and it points to def setup_gallery: self.galleries << Gallery.create and under def create: if #user.save
Fresh eyes on my code would be great.
Users controller:
class UsersController < ApplicationController
respond_to :html, :json
def settings
#user = User.find(id_params)
end
def new
#user = User.new
end
def profile
#profile = User.profile
end
def create
#user = User.new(user_params)
if #user.save
UserMailer.registration_confirmation(#user).deliver
session[:user_id] = #user.id
redirect_to root_url, notice: "Thank you for signing up!"
else
render "new"
end
end
def show
#user = User.find(id_params)
end
def edit
#user = User.find(id_params)
end
def index
#users = User.all
end
def destroy
User.find(id_params).destroy
flash[:success] = "User deleted."
redirect_to users_url
end
def update
#user = if current_user.has_role?(:admin)
User.find(id_params)
else
current_user
end
#user.update_attributes(user_params)
respond_with #user
end
private
def user_params
params.require(:user).permit(:name, :email, :username, :password, :zip_code, :birthday, :role)
end
def id_params
params.require(:id).permit(:name)
end
end
User model:
# models/user.rb
after_create :setup_gallery
def received_messages
Message.received_by(self)
end
def unread_messages?
unread_message_count > 0 ? true : false
end
def unread_messages
received_messages.where('read_at IS NULL')
end
def sent_messages
Message.sent_by(self)
end
# Returns the number of unread messages for this user
def unread_message_count
eval 'messages.count(:conditions => ["recipient_id = ? AND read_at IS NULL", self.user_id])'
end
def to_s; username
end
def has_role?(role_name)
role.present? && role.to_sym == role_name.to_sym
end
def send_password_reset
generate_token(:password_reset_token)
self.password_reset_sent_at = Time.zone.now
save!
UserMailer.password_reset(self).deliver
end
def generate_token(column)
begin
self[column] = SecureRandom.urlsafe_base64
end while User.exists?(column => self[column])
end
private
def setup_gallery
self.galleries << Gallery.create
end
end
photos controller:
class PhotosController < ApplicationController
def new
#photo = Photo.new
end
def create
#photo = Photo.new(photo_params)
#photo.user = current_user
if #photo.save
flash[:notice] = "Successfully created photos."
redirect_to :back
else
render :action => 'new'
end
end
def edit
#photo = Photo.find(id_params)
end
def update
#photo = Photo.find(id_params)
if #photo.update_attributes(photo_params)
flash[:notice] = "Successfully updated photo."
redirect_to #photo.gallery
else
render :action => 'edit'
end
end
def destroy
#photo = Photo.find(id_params)
#photo.destroy
flash
[:notice] = "Successfully destroyed photo."
redirect_to #photo.gallery
end
private
def user_params
params.require(:user).permit(:name)
end
def id_params
params.require(:id).permit(:name)
end
end
After some trial and error I found that I had to change the private method in the user model.
What works is,
Gallery.create(user: self)
Thanks for those who responded to help!