Rails method executing, when it should not? - ruby-on-rails

rails 3.2
In my tickets_controller, I have the following:
def update
#ticket = Ticket.find params[:id]
authorize! :update, #ticket
#ticket.assign_attributes(params[:ticket])
#ticket.customer_info.company = #ticket.customer if #ticket.customer_info
#ticket.admin_context = true
if !params[:ticket_update_type].nil? && params[:ticket_update_type] == 'save_lead_billing'
#ticket.process_lead_billing params
end
if #ticket.save
#ticket.update_attribute(:ticket_type, #ticket.ticket_profile.ticket_type)
redirect_to [:admin, #ticket], notice: success_message
else
#ticket.customer_info_type = 'existing'
#can_update_ticket = can? :update, #ticket
#tag_groups = TagGroup.with_type('ticket').for_company(#ticket.customer_id)
flash.now[:error] = #ticket.errors.full_messages.join(', ')
render action: "show"
end
end
In my ticket.rb model, I have the following:
def process_lead_billing params
if params[:lead_billing]["pre_tax_total"].nil? || params[:lead_billing]["post_tax_total"].nil?
return
end
# handles case where billing infor has not been added to lead ticket
if params[:ticket_update_type] == "save_lead_billing"
lead_billing = LeadBilling.new(
:ticket_id => self.id,
:pre_tax_total => params[:lead_billing]["pre_tax_total"],
:post_tax_total => params[:lead_billing]["post_tax_total"],
:status => 'entered'
)
lead_billing.save!
end
end
And in lead_billing.rb model, I have the following:
class LeadBilling < ActiveRecord::Base
validates_presence_of :pre_tax_total, :post_tax_total
validates_numericality_of :pre_tax_total, greater_than: 0, allow_blank: false, only_integer: false
validates_numericality_of :post_tax_total, greater_than_or_equal_to: :pre_tax_total, allow_blank: false, only_integer: false
The problem, is that when the form is submitted, with the pre_tax_total and post_tax_total empty, I am getting an error message.
From the log file:
Started PUT "/admin/tickets/163812" for 73.83.66.151 at 2016-12-21 22:05:28 +0000
Processing by Admin::TicketsController#update as HTML
and the params are:
[utf8] => ✓
[authenticity_token] => mNt+aI3YInoutup4UsBGZ8zZkeFRYCBZAsxEv4JPvoE=
[ticket] => Array
(
......
)
[time_span] =>
[city] => Draper
[state] => Utah
[admin] => true
[specialty] =>
[services] =>
[inventories] =>
[ticket_update_type] => save_lead_billing
[ticket_id] => 1480720184_0388234_ticket
[lead_billing] => Array
(
[pre_tax_total] =>
[post_tax_total] =>
)
[id] => 163812
From the log file, the error is:
Validation failed: Pre tax total can't be blank, Pre tax total is not
a number, Post tax total can't be blank, Post tax total is not a number from
And then it points me to the line in the tickets_controller.rb, where the processing_lead_billing method is called (correctly), and then to the line in the processing_lead_billing method, where it tries to save the lead billing.
Execution should have halted, when I checked for nil, but it continued to execute. Any ideas?

Rails has a blank? method that is usually preferred when testing form params.
nil? is a ruby method that will only return true if the object is nil, while blank? covers all sorts of use cases (empty string, empty array, empty dictionary and of course nil value).
In your case, the returned value are most likely empty string given how rails work, and so you should test for blank? instead of nil?.

Related

How to fix Error ActiveRecord::RecordNotSaved when calling ActiveRecord#find_or_create_by

I am working on a website and whenever someone forgets a null field in the form I get an error saying
You cannot call create unless the parent is saved
This is the trace:
Application Trace
app/views/technicians/offer_comments/_offer_comment.html.slim:1:in `_app_views_technicians_offer_comments__offer_comment_html_slim__1148413763742950523_70319840794240'
app/views/offer_comments/index.html.slim:2:in `_app_views_offer_comments_index_html_slim___917297770217289302_70319839456700'
app/views/shared/offers/_comments.html.slim:8:in `_app_views_shared_offers__comments_html_slim__3779418887197040636_70319839163900'
app/views/technicians/auctions/show.html.slim:98:in `block in _app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/helpers/application_helper.rb:14:in `rescue in cache'
app/helpers/application_helper.rb:6:in `cache'
app/views/technicians/auctions/show.html.slim:1:in `_app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/controllers/technicians/offers_controller.rb:54:in `update'
The error appears in the first line of this html.slim view:
- offer_comment.read_receipts.find_or_create_by user: current_user
.comment id="offer-#{offer_comment.offer_id}-comment-#{offer_comment.id}"
.by
- if offer_comment.user == current_user
= t ".you"
- else
= t ".not_you"
= " - "
= t '.date', date: time_ago_in_words(offer_comment.created_at)
.content
= raw markdown offer_comment.content
The interesting part is that this error only occurs when I call another object, offers, in the main view in which the previous code is rendered: show.html.slim (last line)
ul#customer-auction-tabs.tabs.clean.collapse(data-tabs)
a#auctions-tabs-chevron href="#"
i#auctions-tabs-chevron-icon.fas.fa-chevron-up
li.tabs-title class=chat_active_class
a#chat-tab href="#chat" aria-selected="true"= t '.tabs.chat'
li.tabs-title class=offer_active_class
a#offers-tab href="#offers"= t '.tabs.offer'
- if comments_count > 0
li.tabs-title class=comments_active_class
a#comments-tab href="#comments"= t '.tabs.comments'
li.tabs-title class=other_active_class
a#other-tab href="#other"= t '.tabs.other'
.auctions.tabs-content data-tabs-content="customer-auction-tabs"
#chat.tabs-panel class=chat_active_class
= render partial: "shared/auctions/chat", locals: { auction: auction }
#offers.tabs-panel class=offer_active_class
= render partial: "shared/offers/new", locals: { offer: offer }
#comments.tabs-panel class=comments_active_class
= render partial: 'shared/offers/comments', locals: { offer: offer }
#other.tabs-panel class=other_active_class
- if auction.offers.count.zero?
= t "ingen andre bud endnu"
= render "shared/offers/other"
.offers= render offers
I don't understand how this works because find_or_create_by is apparently supposed to work even if the object hasn't been saved.
Can someone help me solve this issue, and preferably avoid using logic like find_or_create_by in the view at all?
Here is part of the Offer model:
class Offer < ApplicationRecord
has_paper_trail
belongs_to :auction, -> { with_deleted }, counter_cache: true, touch: true
belongs_to :technician, counter_cache: true, foreign_key: :technician_id
has_one :settings, through: :technician
has_many :comments, -> { order(created_at: :asc) }, class_name: "OfferComment"
has_one :review, as: :target
delegate :rating, to: :review, allow_nil: true
delegate :rating_date, to: :review, allow_nil: true
delegate :rating_comment, to: :review, allow_nil: true
validates :description, presence: true
validates :cents, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :amount_validity
scope :not_by, ->(technician) { where.not(technician: technician) }
Here is also the controller update action that gets called when updating the form with a null field:
class Technicians::OffersController < Technicians::ApplicationController
rescue_from ActiveRecord::RecordNotFound do
render "technicians/auctions/lost", status: 404
end
def update
offer.attributes = offer_params
changed = offer.changed?
if offer.save
OfferUpdatedWorker.perform_async offer.id if changed
flash[:info] = t(".success")
redirect_to [:technicians, auction]
else
flash.now[:error] = t(".failure")
render "technicians/auctions/show",
locals: { auction: auction, offer: offer },
status: 400
end
end
Another important file to note is the auction controller that originally calls "technicians/auctions/show"
def show
render(:lost) && return if lost?
render :show, locals: {
offers: sorted_o,
auction: auction,
#other: other,
offer: offer,
} if stale? **cache_options(auction.id, auction.updated_at)
end
private
#=begin
def sorted_o
#sorted_o ||= begin
field = (%w[cheapest closest guarantee] & [params[:sort]])[0].presence || "cheapest"
case field
when "closest"
auction
.offers
.includes(:auction, :technician, :review)
.sort_by { |o| distance(o.technician, auction) }
when "guarantee"
auction
.offers
.includes(:auction, :technician, :review)
.joins(:settings)
.order("technician_settings.guarantee desc")
else
auction
.offers
.includes(:auction, :technician, :review)
.order(cents: :asc)
end
end
end
#=end
def offer
#offer ||= auction.offers.by(current_user) ||
auction.offers.new(technician: current_user)
end
It looks like you need offer_comment to be saved before a read_receipt that belongs to it can be created, which makes sense - the offer_comment doesn't have an id until it has been saved.
This might get you past the problem.
offer_comment.tap(&:save).read_receipts.find_or_create_by user: current_user
I fixed it. The cause of the problem was that when the update action fails in offers_controller.rb it calls the show view without the offers variable, this variable is somehow related to the offer_comments, but I'm not sure how/why because offer_comments is supposed to be only related to the variable offer and not offers.
However, when I checked the console there were null elements in the offer_comments so I just went on and changed the view to only show non null elements, the error stack then pointed to offers not being defined in the show view.

Undefined method `build_for` for class in Rails

I have a simple app in which I want to allow the admin to create a new company. My create method in the controller is as follows:
def create
#company = Company.find_by({ name: company_create_params[:company_name] })
if #company.nil?
#company = Company.build_for(company_create_params)
else
return render :status => 200, :json => {
:error => true,
:reason => 'This company already exists in the database!'
}
end
if #company.save
return render :status => 200, :json => {
:success => true
}
else
return render :status => 500, :json => {
:error => true,
:reason => 'There was a problem adding the company'
}
end
end
private
def company_create_params
params.require(:company).permit( :company_name, :company_total_credits )
end
And my company model is:
class Company < ActiveRecord::Base
has_many :role
end
But every time I make an API post it gives me an error Undefined methodbuild_forfor class #<....>
Is it because of the has_many relationship? I don't want to add any value for the roles, rather I want them to be able to do it later on. Is there no way to fix this?
ActiveRecord doesn't provide a build_for method, hence the error.
You probably meant build, which is a method defined on collection associations. In this case, you probably want new or create since Company is a model, not an association.
Your whole action could be reduced significantly by following some conventions, by the way:
class Company < ActiveRecord::Base
has_many :roles
validates :company_name, uniqueness: true
end
# controller
def create
#company = Company.new(company_create_params)
if #company.save
render json: { success: true }
else
render status: 500, json: {
error: true,
reason: #company.errors.full_messages.to_sentence
}
end
end

Using less than or greater validation in ruby on rails

I have two models the first model is entry this is used with my controller entry to create a new request.. I need to validate range_days from entry is less than or equal to range from my second model empaccrl..But no matter what I do Still get an undefined method range...
My Entry Model
class Entry < ActiveRecord::Base
self.primary_key = 'id'
validates :indirect_id, presence: true, allow_blank: false
validates :leave_range, presence: true, allow_blank: false
validates :range_days, presence: true, allow_blank: false, length: { maximum: 2 }
# Really wish I could get this validation to work ..............
validates :range_days, :numericality => { :less_than_or_equal_to => :range }, :presence => true
belongs_to :empaccrl
attr_accessible :emp_id, :person_id, :emp_dept, :emp_no, :emp_first_name, :emp_last_name, :emp_mail_addr, :indirect_id, :mgr_no, :mgr_first_name, :mgr_last_name, :mgr_mail_addr, :leave_range, :employee_type, :seq_no, :range_days, :alt_mgr_name, :alt_mgr_addr, :alt_mgr_no
# This is for the validation
def range
empaccrl.range
end
end
My Empaccrl Model
class Empaccrl < ActiveRecord::Base
establish_connection :vdev
self.table_name = 'empaccrl'
belongs_to :entry
attr_accessor :range
end
My Entry Controller with create method and new
def new
#entry = Entry.new
respond_to do |format|
format.html# new.html.haml
format.xml { render :xml => #entry }
end
end
def create
params.permit!
#entry = Entry.new(params[:entry])
respond_to do |format|
if #entry.save
EntryMailer.submit_for_approval(#entry).deliver
format.html { redirect_to(entry_path( #entry ), :notice => 'Entry successfully created.') }
format.xml { render :xml => #entry, :status => :created, :location => #entry }
else
format.html { render :action => "new" }
format.xml { render :xml => #entry.errors, :status => :unprocessable_entity }
end
end
end
This is my entry table
ID NUMBER(38,0)
CREATED_AT DATE
UPDATED_AT DATE
EMP_ID VARCHAR2(255 BYTE)
EMP_NO VARCHAR2(255 BYTE)
EMP_FIRST_NAME VARCHAR2(255 BYTE)
EMP_LAST_NAME VARCHAR2(255 BYTE)
EMP_MAIL_ADDR VARCHAR2(255 BYTE)
INDIRECT_ID VARCHAR2(255 BYTE)
MGR_NO VARCHAR2(255 BYTE)
MGR_FIRST_NAME VARCHAR2(255 BYTE)
RANGE_DAYS NUMBER(38,0)
This is my Empaccrl table
PERSON_ID NUMBER(10,0)
IS_ACTIVE VARCHAR2(1 BYTE)
ACCRUAL_DATE DATE
RANGE NUMBER(10,0)
PROJECTED_ACCRUED NUMBER(12,5)
Any help is greatly appreciated my eyes are burning from staring at this for so long!!!!!
No way to do this using built-in validations. You need to write a custom one:
validates :range_days, :presence => true
validate :range_days_smaller_than_range, if: :range
private
def range_days_smaller_than_range
errors.add(:range_days, "LOL Nah! <Any error message or translation key here>") if range_days > range
end
I got it to work!!!!
This is how I got it to do what I wanted it to do thanks #BroiSatse for the help!
validate :range_days_smaller_than_range, if: :range
def range
range = Empaccrl.where("person_id = ? and is_active = ?", '14052', 'Y').pluck(:range).first
end
private
def range_days_smaller_than_range
errors.add(:range_days, "Sorry you don't have days for this request") if range_days > range
end
end

ruby on rails if statement always returns true

controller code:
def create
if current_user.id != params[:friend_id]
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 1 }
else
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 2 }
end
render :json => ActiveSupport::JSON.encode( #return )
end
so current_user.id is 1 and params[:friend_id] is being passed via an ajax call and its value is also 1
the problem is that it always returns true when it should return false in this case... is there anything that has to do with the number 1 being parsed as string instead of integer?
params are always strings, use:
if current_user.id != Integer(params[:friend_id])
I don't recommend to_i, look why:
"abc".to_i # => 0 which is unexpected
Integer("abc") # => raises error, which is fine

Refactoring a hash to Object

I have a method that return a Hash and then I write the entries of hash in xml file. Iwant to convert this Hash to an object to store the entry and then write it to xml file...
My current code is like this
def entry(city)
{
:loc => ActionController::Integration::Session.new.url_for(:controller => 'cities', :action => 'show', :city_name => city.name, :host => #country_host.value),
:changefreq => 0.8,
:priority => 'monthly',
:lastmod => city.updated_at
}
end
The write_entry method is inside my writer class that writes this entry to xml file
def write_entry(entry)
url = Nokogiri::XML::Node.new( "url" , #xml_document )
%w{loc changefreq priority lastmod}.each do |node|
url << Nokogiri::XML::Node.new( node, #xml_document ).tap do |n|
n.content = entry[ node.to_sym ]
end
end
url.to_xml
end
Thanks
I might be way off here, but it seems like what you're trying to do is something like this:
First, figure out what makes sense as a class name for your new object. I'm going with Entry, because that's the name of your method:
class Entry
end
Then take all the "properties" of your hash and make them reader methods on the object:
class Entry
attr_reader :loc, :action, :changefreq, :priority, :lastmod
end
Next you need to decide how this object will be initialized. It seems like you will need both the city and #country_host for this:
class Entry
attr_reader :loc, :action, :changefreq, :priority, :last mod
def initialize(city, country_host_value)
#loc = ActionController::Integration::Session.new.url_for(:controller => 'cities', :action => 'show', :city_name => city.name, :host => country_host_value)
#changefreq = 0.8 # might actually want to just make this a constant
#priority = 'monthly' # another constant here???
#lastmod = city.updated_at
end
end
Finally add your XML builder method to the class:
class Entry
attr_reader :loc, :action, :changefreq, :priority, :last mod
def initialize(city, country_host_value)
#loc = ActionController::Integration::Session.new.url_for(:controller => 'cities', :action => 'show', :city_name => city.name, :host => country_host_value)
#changefreq = 0.8 # might actually want to just make this a constant
#priority = 'monthly' # another constant here???
#lastmod = city.updated_at
end
def write_entry_to_xml(xml_document)
url = Nokogiri::XML::Node.new( "url" , xml_document )
%w{loc changefreq priority lastmod}.each do |node|
url << Nokogiri::XML::Node.new( node, xml_document ).tap do |n|
n.content = send(node)
end
end
url.to_xml
end
end
Now that your hash has been refactored, you can update your other class(es) to use the new object:
class WhateverClassThisIs
def entry(city)
Entry.new(city, #country_host.value)
end
end
It's not clear how the XML writer method is being called, but you would need to update that as well to use the new write_entry_to_xml method, passing in the xml document as an argument.

Resources