complex validation on object update? - ruby

I have a model "WorkDetail" and db-attrs related to problem are 'name', 'status' and 'approved_status' with datatype all integer and the model class definition is as:-
class WorkDetail < ActiveRecord::Base
enum name: [:smartcheck, :install, :verify]
enum status: [:pending, :inprogress, :complete]
belongs_to :work_order
has_many :cabinets
after_save :work_order_status_update
private
def work_order_status_update
work_detail_count = self.work_order.work_details.count
status_array = self.work_order.work_details.where(status: 2).count
if status_array == work_detail_count
if work_detail_count == 0
self.work_order.update({status: "pending"})
else
self.work_order.update({status: "complete"})
end
else
self.work_order.update({status: "inprogress"})
end
end
end
Now, I would like to add custom validation to be applied for below problem :-
validation should only apply for update process.
If object's name == "smartcheck" and status == "complete" then only the approved_status boolean attribute should be updated to true (the
default is false on migration), else should give error if not
smartchecked and the status is not complete on trying to update
approved_status attr.
Hoping the question makes sense and Thanks !!! in advance guys, Happy Coding.

1) Validation should only apply for update process
after_save :work_order_status_update, :on => :update
2) If object's name == "smartcheck" and status == "complete" then only
the approved_status boolean attribute should be updated to true (the
default is false on migration), else should give error if not
smartchecked and the status is not complete on trying to update
approved_status attr.
def test_status_and_name
if self.name == "smartcheck" and self.status == "complete"
self.update_attributes(approved_status: true)
else
errors[:base] << "smartchecked and the status is not complete on trying to update approved_status attr."
end
end

Related

Mongoid Is it possible to override updated_at timestamp

I am trying to manually edit the "updated_at" field through a rake task
Here is what it looks like:
task :campaigns_updated_at_recovery => :environment do
Dir.foreach('db/raw-data/campaigns/') do |json|
next if json == '.' or json == '..'
file = File.read('db/raw-data/campaigns/'+json)
data_hash = JSON.parse(file)
#p data_hash["_id"]
thisCampaign = Campaign.find(data_hash["_id"])
thisCampaign["channels"].each do |chan|
if chan["updated_at"] < Date.new(2018,04,19)
data_hash["channels"].each do |channel|
if chan["_id"].to_s == channel["_id"]
chan["updated_at"] = Date.parse(channel["updated_at"])
end
end
end
thisCampaign.save
end
end
However when I run this task, the updated_at date is either not changed or updated to today's date.
What am I missing ?
I am using Mongoid and not ActiveRecord by the way
updated_at is updated by mongoid itself in a callback.
You have two solution to work around that.
The easiest solution would be to use set to change the value directly without firing any callback:
thisCampaign.set(channels: thisCampaign['channels'])
The more flexible solution would be to go down to the driver level. The basic idea is:
Campaign.collection.find(_id: data_hash["_id"]).update_one({
'$set' => {
updated_at: yourDate
}
})
Given your example, you would first need to get the full document
thisCampaign = Campaign.collection.find(_id: data_hash["_id"]).first
if thisCampaign
thisCampaign["channels"].each do |chan|
if chan["updated_at"] < Date.new(2018,04,19)
data_hash["channels"].each do |channel|
if chan["_id"].to_s == channel["_id"]
chan["updated_at"] = Date.parse(channel["updated_at"])
end
end
end
end
Campaign.collection.find(_id: data_hash["_id"]).update_one({
'$set' => {channels: thisCampaign["channels"]}
})
end

Ruby how to decrease number of validation checks?

I want to refactor validation checks on Date fields
include Virtus.model
attribute :created_from, Date
attribute :created_to, Date
attribute :updated_from, Date
attribute :updated_to, Date
validate :validate_created_from_is_a_date, if: :created_from
validate :validate_created_to_is_a_date, if: :created_to
validate :validate_updated_from_is_a_date, if: :updated_from
validate :validate_updated_to_is_a_date, if: :updated_to
def validate_created_from_is_a_date
errors.add(:created_from, "not a date") unless created_from.is_a?(Date)
end
def validate_created_to_is_a_date
errors.add(:created_to, "not a date") unless created_to.is_a?(Date)
end
def validate_updated_from_is_a_date
errors.add(:updated_from, "not a date") unless updated_from.is_a?(Date)
end
def validate_updated_to_is_a_date
errors.add(:updated_to, "not a date") unless updated_to.is_a?(Date)
end
as you can see I have 4 attributes which i need to validate for Date I've tried the following, but it's not working as it checks for all cases
validate :validate_date_attributes, if: :any_date_attributes_defined?
def any_date_attributes_defined?
created_from || created_to || updated_from || updated_to
end
def validate_date_attributes
%w(created_from created_to updated_from updated_to).each do |attribute|
errors.add(attribute.to_sym, "not a date") unless attribute.to_sym.is_a?(Date)
end
end
So found a good way to handle such cases, hope it would be useful for others:
validate :created_from, date_attribute: true, if: :created_from
validate :created_to, date_attribute: true, if: :created_to
validate :updated_from, date_attribute: true, if: :updated_from
validate :updated_to, date_attribute: true, if: :updated_to
which will look for DateAttributeValidator here it's
class DateAttributeValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
record.errors[attr_name] << "not a date" unless value.is_a?(Date)
end
end
I really liked it, pretty neat, it's clean and ruby/rails way

Store calculation in database using variables submitted in a form

I'm submitting a simple form with variables that are named in the database.
I am trying to:
Store the submitted variables in the database (which works fine)
Run a calculation, then store that value into the database
No matter what I try I either get an error or 'nil' (upon #kcmil.inspect) as my result for #kcmil. I'm assuming in my current code i'm not passing the variables to the model, but it doesn't work even when it's in the controller.
I'm at a loss here. My variables that are submitted in the form store just fine as expected. I just want to be able to use submitted variables from a form (that are also database items that get stored upon submission) and before saving to the database (or after, should it matter?) run a calculation and store the result in a database item (that is not previously called in or saved from the form). Does that make sense? Any help or hints are GREATLY APPRECIATED!!
Here are my current calculators_controller create and edit actions:
def create
#calculator = Calculator.new(calc_params)
if #calculator.save
flash[:notice] = "Calculation created successfully."
redirect_to(:action => 'index')
else
render('new')
end
end
def update
#calculator = Calculator.find(params[:id])
if #calculator.update_attributes(calc_params)
flash[:notice] = "Calculation updated successfully."
redirect_to(:action => 'index', :id => #calculator.id)
else
render('edit')
end
end
private
def calc_params
params.require(:calculator).permit(:subsection, :amps, :volts, :distance, :vdrop, :phase, :kcmil, :section_id)
end
Here's my model
class Calculator < ActiveRecord::Base
before_create :kcmil_calc
def kcmil_calc
if #phase == 1
self.kcmil = ((13 * #distance.to_i * #amps.to_i ) / #vdrop.to_i).round(2)
else
self.kcmil = ((12.9 * #distance.to_i * #amps.to_i ) / #vdrop.to_i).round(2)
end
end
end
I HAVE IT! I HAVE IT!
before_update :defaults
def defaults
if self.phase == 1
self.kcmil = ((12.9 * distance * amps) / vdrop).round(2)
else
self.kcmil = ((13 * distance * amps) / vdrop).round(2)
end
end
solved it! I had to call self.phase instead of #phase and change before_create to before_update to get it to work . No change in the controller required. Dang - one simple #! I also removed the to_i because it's not needed since my views prevent me from submitting anything other than integers.

How to extend a custom rails method?

I have a Rails model called Projects:
class Project < ActiveRecord::Base
that has a variety of toggle switches, such as active, started, paid, etc.
I then have a method to return the status in human readable format:
def status
return 'Pending' if self.pending?
return 'Started' if self.started
return 'In Review' if self.in_review?
return 'Approved' if self.approved
return 'Active' if self.active
end
Right now I have another method called status! that returns the same information but in symbol form, which is inefficient in my mind:
def status
return :pending if self.pending?
return :started if self.started
return :awarded if self.awarded
return :in_review if self.in_review?
return :approved if self.approved
return :active if self.active
end
What I would obviously like to do is something more like status.to_sym but can't figure out how to make that happen.
Any thoughts?
How about this:
def status
return 'Pending' if self.pending?
return 'Started' if self.started
return 'In Review' if self.in_review?
return 'Approved' if self.approved
return 'Active' if self.active
end
def status!
# added gsub otherwise 'In Review' is returned as ':in review'
status.gsub(/\s+/, "_").downcase.to_sym
# status.parameterize.underscore.to_sym <- another option, Rails only
end
At first I highly double these methods are efficient.
These methods are to define a certain status at a workflow. In common sense they are mutually exclusive. That is, a project "in pending" could not be a project "active", or "in review", or any other status in this group.
Based on above, why don't you set all of these status as an Enum attribute "status" in this model? This attribute value could be any one within "pending, active, started..." By this you use one field to replace 5 fields.
Then it's easy to get the status in human readable format directly in view, even without a controller method.
<strong>Status: </strong><%= #project.status.titleize %>

Setting an attribute to 0 if a checkbox is checked in Rails

I have a db column called starting_pt that is a decimal.
I want a checkbox, that if checked, will set starting_pt = 0.0
I have set-up the a virt attr in my Model and added it to the accesible's
attr_acessor :reset
attr_accessible :reset
I've set up
before_create :reset_starting_pt, :if => :reset?
def reset?
#reset == "1"
end
def reset_starting_pt
#starting_pt = 0.0
end
This is assuming that a checked checkbox is equal to "1" and an unchecked is equal to "0".
This solution is not working. Any guidance anyone can provide on how to do do this would be very helpful, thanks!
You can create a setter method for the reset which when supplied with "0" will reset your #starting_pt instance variable.
def reset= val
#starting_pt = 0.0 if 0 == val.to_i
end

Resources