I encountered an issue today I haven't seen before - I have a custom validation to check if a discount code has already been used in my Order model:
validate :valid_discount_code
def valid_discount_code
is_valid = false
code = nil
if discount_code.present?
if discount_code.try(:downcase) == 'xyz'
code = 'xyz'
is_valid = true
else
code = Coupon.find_by_coupon_code(discount_code)
is_valid = code.present?
end
if code.nil?
is_valid = false
errors.add(:discount_code, "is not a valid referral code")
elsif ( code.present? && Coupon.where(email: email, coupon_code: code).present? )
is_valid = false
errors.add(:discount_code, "has already been used.")
end
puts "------------"
puts errors.full_messages ## successfully 'puts' the correct error message into my console.
puts "------------"
if is_valid
.... do stuff.....
end
end
end
In my controller:
if current_order.update_attributes(discount_code: params[:coupon_code].downcase, another_attribute: other_stuff)
....
session[:order_id] = nil
render json: { charged: 'true' }.as_json
else
puts "==============="
puts current_order.id # shows the correct current order ID
puts current_order.errors.full_messages # shows that there are no errors present
puts "==============="
render json: { charged: 'false', errors: current_order.errors.full_messages }.as_json
end
So it looks like at update_attributes, it runs the validation, fails the validation, creates the error message, and then once it's back at my controller the error message is gone. I'm stumped as to what can be causing that issue.
EDIT:
Here is what current_order is:
In ApplicationController.rb:
def current_order
session[:order_id].present? ? Order.find(session[:order_id]) : Order.new
end
Looks like every time you call current_order it reruns the find method. You can confirm this in the logs, but try not to call that, or at least memoize it. In an instance variable, the same order will be used everytime.
def current_order
#current_order ||= (Order.find_by_id(session[:order_id]) || Order.new)
end
Related
I have created a Rails API and when I run either this path http://localhost:3000/api/v1/records or http://localhost:3000/api/v1/items in the browser address bar, I get the error as displayed in the image below
Below is the entire code in the application_controller.rb file
class ApplicationController < ActionController::API
SECRET_KEY = Rails.application.credentials.jwt[:secret].to_s
EXPIRES_IN = Rails.application.credentials.jwt[:expires_in]
def authorized
render json: {message: 'Please log in'}, status: 401 unless logged_in?
end
def encode_token(payload)
payload[:exp] = EXPIRES_IN.days.from_now.to_i
JWT.encode(payload, SECRET_KEY, 'HS256')
end
def auth_header
return unless request.headers['Authorization']
request.headers['Authorization'].split(' ')[1]
end
def decoded_token
return unless auth_header
token = auth_header
begin
JWT.decode(token, SECRET_KEY, true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
def logged_in_user
return unless decoded_token
user_id = decoded_token[0]['user_id']
#current_user = User.find_by(id: user_id)
end
def logged_in?
logged_in_user ? true : false
end
end
Any help on how I can fix this will be greatly appreciated. This is the first Rails API I am building.
Thanks
The error message is actually very precise. It tells you that there is no method [] defined on nil and it tells you where ther error is happening: in the second line of the ApplicationController.
The second line of that controller looks like this:
SECRET_KEY = Rails.application.credentials.jwt[:secret].to_s
That means if Rails compains about calling [] on a nil value then
Rails.application.credentials.jwt
must return nil.
It looks like you haven't setup the secret credentials correctly or the format of the file is somehow broken.
I suggest reading about Custom Credentials in the Rails Guides.
I'm trying not to save a record in the database if an exception is raised but for some reason it is ignored. The record save anyway. What am I doing wrong? Any help is appreciated. Thanks!
# This is my controller
# POST /users
def create
service_action(success: #support_user, fail: :new) do |service|
service.create_user
end
end
def service_action(page)
result = yield(SupportUserService.new(#support_user, App.logger))
if result[:ok]
redirect_to page[:success], :notice => result[:message]
else
flash[:error] = result[:message] if result[:message].present?
render page[:fail]
end
end
#This is in a service class -> SupportUserService
def create_user
return_hash(:create) do |h|
if user.save
grant_ssh_access(user.login, user_ssh_keys!)
h[:ok] = true
h[:message] = "Support User '#{user.login}' was successfully created."
end
end
end
def return_hash(action)
{ok: false, message: ''}.tap do |h|
begin
yield h
rescue => e
h[:ok] = false
h[:e] = e
h[:message] = "Failed to #{action} support user: #{e.message}"
logger.error(stacktrace("Failed to #{action} support user", e))
end
end
end
Replace #save with exception raise version #save!, and remove if clause, so you'll get:
return_hash(:create) do |h|
user.save!
grant_ssh_access(user.login, user_ssh_keys!)
h[:ok] = true
h[:message] = "Support User '#{user.login}' was successfully created."
end
This will throw an exception on save failure, and you will be able to trap it in the #return_hash.
I fixed this by moving the grant_ssh_access function outside of the user.save method. That way if that function returns an exception it gets trapped before reaching the save method and exits.
I have several actions in my hotel_controller where I call an API to get back data. I created different services to keep my API calls outside my controller logic. For every API calls I have got some "general response errors" like unauthorized or not found for instance. As these errors are common to all API calls, I wanted to create a private method to deal with them in my hotel controller:
private
def global_error_checking(response)
if response.message == "Unauthorized"
redirect_to unauthorized_path and return
elsif response.message == "Not Found"
redirect_to not_found_path and return
else
end
end
Then in every method of my controller where it's needed I would call the global_error_checking method before checking for specific errors. For instance :
def index
service = Hotels::GetHotelListService.new( account_id: params[:account_id],
user_email: session[:user_email],
user_token: session[:user_token]
)
#response = service.call
global_error_checking(#response)
if #response["hotels"].blank?
flash[:notice] = "You have not created any hotels yet !"
redirect_to account_path(params[:account_id])
else
#hotels = #response["hotels"]
#account = #response["account"]
end
end
The problem is that after executing global_error_checking, the action of the controller goes on and does not stop even though a condition of global_error_checking is satisfied.
1) How can I stop the execution of the whole controller method if a condition inside global_error_checking is satisfied ?
2) Is there maybe a better way to achieve this ?
I wouldn't name the parameter "response" since that's already being used by the controller.
The other thing I noticed is that you're accessing this "#response" in different ways which might be ok but it looks wrong. In your global_error_checking method you're accessing it's properties using dot syntax (response.message), however in your controller action you're accessing it as if it were a hash. Again, this might be ok depending on its data type.
If I were you, I would refactor this to look like:
class SomeController < ApplicationController
def index
#hotels = some_resource['hotels']
#account = some_resource['account']
end
private
def some_resource
#_some_resource ||= begin
service = Hotels::GetHotelListService.new({
account_id: params[:account_id],
user_email: session[:user_email],
user_token: session[:user_token]
})
result = service.call
if result['message'] == 'Unauthorized'
redirect_to unauthorized_path and return
elsif result['message'] == 'Not Found'
redirect_to unauthorized_path and return
else
result
end
end
end
end
You can use return statement:
return if global_error_checking in your controller
and case statement in private method with some changes:
private
#returns true after redirecting if error message "Unauthorized" or "Not Found"
def global_error_checking(response)
case response.message
when "Unauthorized"
redirect_to unauthorized_path and return true
when "Not Found"
redirect_to not_found_path and return true
end
end
I have a fairly straightforward if else statement in a controller as follows:
if citation_array.blank?
flash.now[:error] = "There was a problem saving the publications selected!"
#user = current_user
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
publication = Publication.new
render_publication(user_publication)
publication.citation = user_publication
publication.user_id = current_user.id
publication.title = #title
publication.authors = #authors
publication.journal = #journal
publication.year = #year
publication.volume = #volume
publication.pages = #pages
if publication.save
next
end
rescue
next
end
end
#user = current_user
redirect_to current_user
return false
end
It is served an array of id's in citation_array and if there are values present it loops throught them saving each publication found by the id's in the array. The render_publication method instantiates the instance variables so don't be concerned with that.
My issue is this. Very rarely an id is fake or wrong and so this block fails at that point. I want to simple move on to the next id in the array and forget about the failed id. I don't even need to save an exception. I'm new to Ruby (coming from a PHP background).
I want to check if this syntax is correct. I am having trouble checking it in the rails console.
Syntax errors are easier to spot if the code is indented correctly.
if citation_array.blank?
flash.now[:error] = "There was a problem saving the publications selected!"
#user = current_user
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
publication = Publication.new
render_publication(user_publication)
publication.citation = user_publication
publication.user_id = current_user.id
publication.title = #title
publication.authors = #authors
publication.journal = #journal
publication.year = #year
publication.volume = #volume
publication.pages = #pages
if publication.save
next
end
rescue
next
end
end
#user = current_user
redirect_to current_user
return false
end
The syntax seems correct. Though an easier way to find out would have been just to run the code.
Some things in the code are not necessary though. After cleaning up your code a bit, it would look something like this with the same functionality.
#user = current_user
if citation_array.blank?
flash.now[:error] = 'There was a problem saving the publications selected!'
render 'pubmed_search'
else
citation_array.each do |user_publication|
begin
render_publication(user_publication)
Publication.create!( # create! here so that if something does go wrong, then you're not just ignoring it, but you can log it in your rescue block.
citation: user_publication,
user_id: current_user.id,
title: #title,
authors: #authors,
journal: #journal,
year: #year,
volume: #volume,
pages: #pages
# This hash should be extracted to a method.
)
rescue
# Just doing nothing here is valid syntax, but you should at least log your error.
end
end
redirect_to current_user
false # This can most likely be omitted as well since not many places care about the return value of a controller action.
end
Syntax for begin-rescue,
begin
your code...
rescue => e
Rails.logger.debug 'Exception is #{e}'
end
Here is our code to save quite a few objects all at once within one transaction. What the code does is to create a new checkout record (for warehouse) and update each item (there may be a few of them) in stock. Since all save has to be either all or none, we put all the save within Rails transaction:
#checkout = RequisitionCheckoutx::Checkout.new(params[:checkout])
#checkout.last_updated_by_id = session[:user_id]
#checkout.checkout_by_id = session[:user_id]
#checkout.transaction do
params['ids'].each do |id|
params['out_qtys'].each do |qty| #ids passed in as a string of 'id'
stock_item = RequisitionCheckoutx.warehouse_class.find_by_id(id.to_i)
qty = qty.to_i
if stock_item.stock_qty >= qty
stock_item.stock_qty = stock_item.stock_qty - qty
stock_item.last_updated_by_id = session[:user_id]
begin
stock_item.save
rescue => e
flash[:notice] = t('Stock Item#=') + id.to_s + ',' + e.message
end
end
end unless params['out_qtys'].blank?
end unless params['ids'].blank?
if #checkout.save
redirect_to URI.escape(SUBURI + "/authentify/view_handler?index=0&msg=Successfully Saved!")
else
flash[:notice] = t('Data Error. Not Saved!')
render 'new'
end
end
We haven't run the test yet and the code looks not pretty. Is there a better way to handle this kind of batch save? Also should the rescue loop be removed for transaction?
The transaction block should be performed first and then you should deal with action response. Besides that, catching exception here is pointless, cause using save returns simply true or false. Your transaction should look like:
RequisitionCheckoutx::Checkout.transaction do
begin
#...
#...
stock_item.save! # it will raise RecordInvalid or RecordNotSaved if something goes wrong
#...
#...
#checkout.save!
rescue Exception => e
raise ActiveRecord::Rollback # it calls Rollback to the database
end
end
Now, using ActiveModel::Dirty you need to check if #checkout has been saved:
if !#checkout.changed?
redirect_to "/something"
else
flash[:notice] = t('Data Error. Not Saved!')
render 'new'
end