How can I validate an attribute to be between various noncontinuous ranges? - ruby-on-rails

I'm wanting to validate that my height attribute is within a bunch of different ranges. So my attempt was something like what I did below... however this is incorrect. How should this be done? Thanks!
validates :height, :numericality => { in: { 5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118,
6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118,
7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118 } }

You can put that in a custom validate method:
class YourModel < ActiveRecord::Base
VALID_HEIGHT_RANGES = [5020..5028, 5030..5038, 5040..5048, 5050..5058, 5060..5068, 5070..5078, 5080..5088, 5090..5098, 5100..5108, 5110..5118, 6000..6008, 6010..6018, 6020..6028, 6030..6038, 6040..6048, 6050..6058, 6060..6068, 6070..6078, 6080..6088, 6090..6098, 6100..6108, 6110..6118, 7000..7008, 7010..7018, 7020..7028, 7030..7038, 7040..7048, 7050..7058, 7060..7068, 7070..7078, 7080..7088, 7090..7098, 7100..7108, 7110..7118]
validate :height_in_valid_range
private
def height_in_valid_range
VALID_HEIGHT_RANGES.each do |range|
unless range.include? height
errors.add :height, "not in valid range"
break
end
end
end
end

Related

Custom Rails Dashboard, how to optimize data retrieval to display in view?

I am making a custom dashboard for a school application that requires me to calculate some KPIs, the way am doing it right now is calling several class methods from the Opportunity class in the dashboard/index action from the controller, and storing each method result in a variable that is going to be used in a tile. So each variable is a different tile of the dashboard.
The methods belong to the Opportunity class shown below:
class Opportunity < ApplicationRecord
belongs_to :organization
belongs_to :opportunity_status
has_many :tasks, dependent: :destroy
has_many :opportunity_status_logs, dependent: :destroy
before_create :create_status_log
after_update :create_status_log, if: :opportunity_status_id_changed?
validates :name, :description, :revenue, :opportunity_status_id, :closing_date, presence: true
validates :name, :description, format: { with: /\A[[:alpha:]a-zA-Z0-9ñÑ#()\-.,\s]+\z/ }
validates :revenue, numericality: true
validates :closing_date, inclusion: { in: (Time.zone.today..Time.zone.today+5.years) }
def create_status_log
OpportunityStatusLog.create(opportunity_id: self.id, opportunity_status_id: self.opportunity_status_id)
end
def status_updated_by(user)
#status_log = self.opportunity_status_logs.last
#status_log.user_id = user.id
#status_log.save!
end
def self.actives
self.where.not(opportunity_status_id: [11,12])
end
def self.won
self.where(opportunity_status_id: 11)
end
def self.lost
self.where(opportunity_status_id: 12)
end
def self.average_revenue
self.won.average(:revenue)
end
def self.minimum_revenue
self.won.minimum(:revenue)
end
def self.maximum_revenue
self.won.maximum(:revenue)
end
def self.filter_by_status(status_id)
self.where(opportunity_status: status_id)
end
def self.relative_percentage(item_amount, total)
item_amount * 100 / total
end
def self.conversion_rate
self.won.count / self.all.count.to_f * 100
end
def self.potential_revenue
self.actives.sum(:revenue)
end
end
and this is the way the controller is structured:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
#opportunities = Opportunity.includes(:opportunity_status).all
#actives = Opportunity.actives.count
#won = Opportunity.won.count
#lost = Opportunity.lost.count
#average_revenue = Opportunity.average_revenue
#minimum_revenue = Opportunity.minimum_revenue
#maximum_revenue = Opportunity.maximum_revenue
#in_appreciation = Opportunity.filter_by_status(6).count
#in_value_proposition = Opportunity.filter_by_status(7).count
#in_management_analysis = Opportunity.filter_by_status(8).count
#in_proposal = Opportunity.filter_by_status(9).count
#in_review = Opportunity.filter_by_status(10).count
#app_perc = Opportunity.relative_percentage(#in_appreciation, #opportunities.count)
#vp_perc = Opportunity.relative_percentage(#in_value_proposition, #opportunities.count)
#ma_perc = Opportunity.relative_percentage(#in_management_analysis, #opportunities.count)
#pp_perc = Opportunity.relative_percentage(#in_proposal, #opportunities.count)
#rw_perc = Opportunity.relative_percentage(#in_review, #opportunities.count)
#conversion_rate = '%.2f' % [Opportunity.conversion_rate]
#potential_revenue = Opportunity.potential_revenue
end
end
Even though it works as expected, it looks like the controller is a bit too fat and I feel that with the current approach if the app scales it will be very slow due to the amount of queries that are being done. So, is there a way to refactor this in order to optimize the data retrieval and the displaying of the KPIs?
Thanks in advance
You can try implementing Facade Pattern in Rails. It will make your controller skinny but on the query part you will still be needing to make those queries, there is no way to skip that.
You can try to optimize db by adding index and creating sql views in future when you get performance lag, at this time it will be like premature optimization

Rails: Undefined method in model

I'd like to convert a unix time to human time before saving my object from an api.
But I cannot access to my method format date, it raise me :
undefined method `format_date' for 1467738900000:Fixnum
My model :
class Conference < ActiveRecord::Base
validates_presence_of :title, :date
validates :date, :uniqueness => true
def self.save_conference_from_api
data = self.new.data_from_api
self.new.parisrb_conferences(data).each do |line|
conference = self.new
conference.title = line['name']
conference.date = line['time'].format_date
conference.url = line['link']
if conference.valid?
conference.save
end
end
self.all
end
def format_date
DateTime.strptime(self.to_s,'%Q')
end
line['time'] is not an instance of your Conference class, so you can't call format_date method on it. Instead, for example, you can make format_date a class method:
def self.format_date str
DateTime.strptime(str.to_s,'%Q')
end
And then call it like this:
conference.date = format_date(line['time'])
The other option is to use a before_validation callback (attribute assignment will be as follows: conference.date = line['time'] and there is no need for format_date method):
before_validation -> r { r.date = DateTime.strptime(r.date.to_s,'%Q') }
You are getting the date in unix time milliseconds. You can do like this
conference.date = DateTime.strptime(line['time'].to_s,'%Q')

Conditional Validation RAILS MODEL

In my model file, I am trying to make a conditional validation depending on a hidden form value. It seems like the :form_type_main? method never gets called, or just does not work. Want am I doing wrong?
attr_accessor(:form_type,:field1,:field2,:field3,:field4)
required_main = ["field1", "field2"]
required_second = ["field3", "field4"]
if :form_type_main?
required = required_main
else
required = required_second
end
required.each do |i|
validates_presence_of i
end
def form_type_main?
form_type == "main"
end
You may simply define the validations like this:
REQUIRED_MAIN = [:field1, :field2]
REQUIRED_SECOND = [:field3, :field4]
validates_presence_of *REQUIRED_MAIN, if: :form_type_main?
validates_presence_of *REQUIRED_SECOND, unless: :form_type_main?
def form_type_main?
form_type == "main"
end

.save throws an error instead of execute the else part of the conditional

Validation for uniqueness applies to a combination of two fields. My problem is that patching a new record that does validate, throws the error ActiveRecord::RecordNotUnique PG::UniqueViolation: ERROR: duplicate key value violates unique constraint, rather than that it executes the else part of the method below. Why does it throw an error instead of execute the else part? How to change this?
def create
first_node = Node.find_by(id: params[:first_node_id])
second_node = Node.find_by(id: params[:second_node_id])
link = first_node.where_first_links.build(create_params)
if link.save
render json: link, status: :created
else
render json: link, message: "unable", status: :bad_request
end
end
In the migration file:
add_index :links, [:first_node_id, :second_node_id], unique: true
The model validation:
before_save :order_nodes
validates :first_node_id, presence: true
validates :second_node_id, presence: true
validates :first_node_id, uniqueness: { scope: :second_node_id }
def order_nodes
if first_node_id > second_node_id
first = first_node_id
second = second_node_id
self.first_node_id = second
self.second_node_id = first
if direction == '0'
self.direction = 1
elsif direction == '1'
self.direction = 0
end
end
end
It most probably means that:
Your object passed the validation.
THEN your before_save callback reordered the fields
Subsequent attempt to save the record to the database violated the database unique constraint
Try changing your callback from:
before_save :order_nodes
to:
before_validation :order_nodes
NOTE: In this case, you'll have to assume that your fields may be invalid and rewrite your callback accordingly.

Undefined method delete_if for NilClass

I'm trying to validate redemption codes
#redemption_codes = Account.where(:redeemed == false).map(&:redemption_code)
validates :code, inclusion: { in: #redemption_codes }
before_create :remove_code
def remove_code
#redeemed = Account.where(:redeemed == true).map(&:redemption_code)
#redemption_codes.delete_if{|code|#redeemed.include?(code)}
end
If a code has already been redeemed, I want to remove it from the array, so it's no longer included.
Here I get
Undefined method delete_if for nil:NilClass
The value of #redemption_codes is nil
If I type in a value that is not included in the array, I get an error flash message.
When I type in a value included in the array, it works. In other words, it works if I comment out the remove_code method.
Problem is #redemption_codes is set in stone. If a value is updated from :redeemed = false to :redeemed = true, it won't disappear from the array.
That's why I tried to create a custom validation message, but it didn't work.
Help is appreciated.
That is the incorrect syntax.
Account.where(:redeemed == false)
should be
Account.where(redeemed: false)
You should try something like this
def validate_code
#redemption_codes = Account.where(redeemed: false).map(&:redemption_code)
errors[:base] << "Invalid Redemption Code" unless #redemption_codes.include?(self.code)
end
You could also use the uniqueness validator
validates :code, inclusion: { in: #redemption_codes }, uniqueness: true
This ensures that a redeemed account will not be valid

Resources