I am using the gem Alchemist to make unit conversions.
Given this working in my model:
class Item < ApplicationRecord
def converted
quantity = 1
quantity.kg.to.g
end
end
How do I make kg and g dynamic? Like:
quantity.unit_purchase.to.unit_inventory
unit_purchase and unit_inventory are attributes (strings) of the class, corresponding to values such as kg, g and so on.
So perhaps something like:
x = self.unit_purchase
y = self.unit_inventory
quantity.x.to.y
But I'm having hard time to find the syntax.
If you really want to do this the hard way:
unit_purchase = :kg
unit_inventory = :g
quantity.send(unit_purchase).to.send(unit_inventory)
That depends on knowing with absolute certainty that the two arguments are valid and aren't something hostile supplied by the user.
A safer way is to define a more arbitrary conversion method like:
quantity.convert(from_unit: unit_purchase, to_unit: unit_inventory)
Where that can check the arguments accordingly and raise on unexpected values.
Related
I have a model Tippani, which belongs to another model Fiscal Year.
Tippani.rb
class QuotationTippani < ApplicationRecord
belongs_to :fiscal_year
end
I have two instances of tippani class, that belong to the same fiscal year.
I want to get the instances of tippani class that belongs to the same fiscal year.
I tried something like this:
Tippani.where(fiscal_year_id == 4)
But I get an error
undefined local variable or method `fiscal_year_id' for main:Object
Also, is there some query method, where I could get all the tippani instances, whose fiscal year is less than 2073 or some number.
Something like this:
Tippani.fiscal_year.where(year < 2074)
You need to pass Hash into your query method, like this:
QuotationTippani.where(fiscal_year_id: 4)
or, if you have FiscalYear instance available and Fiscalyear#quotation_tippanies association is set up:
fiscal_year.quotation_tippanies
About the filtering by year, it's about using joins and passing parameters properly:
QuotationTippani.joins(:fiscal_year).where('fiscal_years.year < ?', 2074)
Generally speaking, I advice you to read this guide:
https://guides.rubyonrails.org/active_record_querying.html
You'll find all the information you need here.
== is the comparison operator. When you write:
Tippani.where(fiscal_year_id == 4)
Ruby treats fiscal_year_id as an identifier and tries to find either a local variable or a method named fiscal_year_id and checks if it is equal to 4. So even if you had assigned fiscal_year_id you're calling:
Tippani.where(true) # or false
Which isn't even close since you want evaluate WHERE tippanis.fiscal_year_id = 4 in the database.
Hashes in Ruby use hashrockets (=>) or colons (:):
# you can assign any type of key with hashrockets
{ :foo => :bar, 1 => "first", true => "yes", false => "no" }
# colons coerce the keys to symbols
{ a: 1, b: 2, c: 3, "this gets cast to a symbol": 4 }
Colons are generally preferred. When you call a method you can omit the braces as long as the hash is the last argument.
Tippani.where({ :fiscal_year_id => 4 }) # lots of noise
Tippani.where(fiscal_year_id: 4) # better
Also, is there some query method, where I could get all the tippani
instances, whose fiscal year is less than 2073 or some number.
Something like this:
Tippani.fiscal_year.where(year < 2074)
Again this won't work since < is an operator and the result of evaluating the expression is true or false.
ActiveRecord does not really have an elegant way of handling LT, GT, LTE and GTE conditions. So you either need to use a string:
Tippani.joins(:fiscal_year).where('fiscal_years.year < ?', 2074)
Or use Arel:
Tippani.joins(:fiscal_year).where(FiscalYear.arel_table[:year].lt(2074))
You need joins
Tippani.joins(:fiscal_year).where(fiscal_years: { id: 4 })
Second question also can be handled in similar way.
Tippani.joins(:fiscal_year).where("fiscal_years.year < ?", 2074)
Hope that helps!
I have several calculated values as part of my risk.rb model
before_save :calculate_risk
def calculate_risk
self.risk1_total = self.component1 * self.component2 * self.component3
self.risk2_total = self.component4 * self.component5 * self.component6
...
end
I want to be able to create a risk without filling out the form completely thus each of those components would be nil. So this method creates an error because * is not a valid method for a nil. What is the best way to handle this? I have considered
def calculate_risk
if self.component1.nil? || self.component2.nil? || self.component3.nil?
self.risk1_total = self.component1 * self.component 2 * self.component3
elsif ...
end
However, this is obviously inefficient and repetitive. I also considered initializing all of these values, though I do not know the most efficient way of doing this.
You can do something like the following:
before_save :calculate_risk
def calculate_risk
self.risk1_total = [self.component1,self.component2,self.component3].compact.inject(:*)
self.risk2_total = [self.component4,self.component5,self.component6].compact.inject(:*)
...
end
This is assuming you want nil values to just be dropped from the calculation. This will give a result of nil if all values are nil. You could replace the nils with zeroes if you prefer. You may also be interested in the :reject method or other cools tools in the Ruby Array and Enumerable classes.
I hope that helps.
I am trying to calculate a weighted average of a variable in my model based on a second variable in my model and I'm having trouble finding a way to do it through ActiveRecord.
class Employer < ActiveRecord::Base
attr_accessible :name, :number_of_employees, :average_age
def self.wt_avg_age
#return sum(number_of_employee * average_age)/sum(number_of_employees)
end
end
In straight SQL, I would use:
SELECT id, SUM(number_of_employees*average_age)/SUM(number_of_employees)
FROM employer
GROUP BY name
Can I execute something like this on an ActiveRecord relation in an eloquent way (i.e., without pulling down separate arrays and iterating through every record to get my numerator)? I have tried different combinations using .select(), .pluck(), and sum() without any luck. I'm having trouble getting the ActiveRecord object to perform the sumproduct.
You should be able to do something like:
Employer.select("name, (SUM(number_of_employees*average_age)/SUM(number_of_employees)) as sum").group(:name)
That will return Employer instances to you, but they will only have the .name and .sum attributes on them. This will run the exact SQL query that you wanted.
It looks like ActiveRecord::Calculations#sum takes a block:
# File activerecord/lib/active_record/relation/calculations.rb, line 92
def sum(*args)
if block_given?
self.to_a.sum(*args) {|*block_args| yield(*block_args)}
else
calculate(:sum, *args)
end
end
(also see http://api.rubyonrails.org/classes/Enumerable.html#method-i-sum)
So you might try:
def self.wt_avg_age
numerator = self.all.sum { |e| e.number_of_employee * e.average_age }
denominator = self.sum :number_of_employees
return numerator / denominator
end
Take a try, maybe it can works:
def self.wt_avg_age
a = Employer.sum("number_of_employee * average_age")
b = Employer.sum('number_of_employees')
a/b
end
My search includes the ability to scope a query by a price range, the day of the week, and a guest count.
However, the price is different on each day of the week, and the price changes (not linearly) by the number of guests. The best way I can think of to index the data is to create a different field for each day/guest combo, e.g.,
searchable do
integer :price_sunday_2_guests do
price(:sunday, 2)
end
integer :price_sunday_3_guests do
price(:sunday, 3)
end
...
integer :price_monday_2_guests do
price(:monday, 2)
end
...
# and so on...
end
Obviously, I don't want to type all that in. I want to construct those attributes in the searchable blocks. More like:
searchable do
Date::DAYNAMES.each do |day_name|
day = DateTime.strptime(day_name, '%A')
(guests_min..guests_max).each do |guests|
sym = (day_name.downcase + '_' + guests.to_s + 'guests_price_abs_max').to_sym
integer sym do
price(day_name, guests)
end
end
end
end
But I get the following exception:
NoMethodError: undefined method `guests_min' for #<Sunspot::DSL::Fields:0x000001080bdcf0>
It does not complain about the constant Date::DAYNAMES.
It seems that Sunspot tries to interpret any method token as a field. Which, I suppose would even make sense if I understood it better.
So, my question, is there a smart way for me to do this? Do I need to just hardcode a range and let my price method return an empty value? Is there some mechanism available in the searchable block I'm not aware of?
I am trying to use a time_select to input a time into a model that will then perform some calculations.
the time_select helper prepares the params that is return so that it can be used in a multi-parameter assignment to an Active Record object.
Something like the following
Parameters: {"commit"=>"Calculate", "authenticity_token"=>"eQ/wixLHfrboPd/Ol5IkhQ4lENpt9vc4j0PcIw0Iy/M=", "calculator"=>{"time(2i)"=>"6", "time(3i)"=>"10", "time(4i)"=>"17", "time(5i)"=>"15", "time(1i)"=>"2009"}}
My question is, what is the best way to use this format in a non-active record model. Also on a side note. What is the meaning of the (5i), (4i) etc.? (Other than the obvious reason to distinguish the different time values, basically why it was named this way)
Thank you
You can create a method in the non active record model as follows
# This will return a Time object from provided hash
def parse_calculator_time(hash)
Time.parse("#{hash['time1i']}-#{hash['time2i']}-#{hash['time3i']} #{hash['time4i']}:#{hash['time5i']}")
end
You can then call the method from the controller action as follows
time_object = YourModel.parse_calculator_time(params[:calculator])
It may not be the best solution, but it is simple to use.
Cheers :)
The letter after the number stands for the type to which you wish it to be cast. In this case, integer. It could also be f for float or s for string.
I just did this myself and the easiest way that I could find was to basically copy/paste the Rails code into my base module (or abstract object).
I copied the following functions verbatim from ActiveRecord::Base
assign_multiparameter_attributes(pairs)
extract_callstack_for_multiparameter_attributes(pairs)
type_cast_attribute_value(multiparameter_name, value)
find_parameter_position(multiparameter_name)
I also have the following methods which call/use them:
def setup_parameters(params = {})
new_params = {}
multi_parameter_attributes = []
params.each do |k,v|
if k.to_s.include?("(")
multi_parameter_attributes << [ k.to_s, v ]
else
new_params[k.to_s] = v
end
end
new_params.merge(assign_multiparameter_attributes(multi_parameter_attributes))
end
# Very simplified version of the ActiveRecord::Base method that handles only dates/times
def execute_callstack_for_multiparameter_attributes(callstack)
attributes = {}
callstack.each do |name, values|
if values.empty?
send(name + '=', nil)
else
value = case values.size
when 2 then t = Time.new; Time.local(t.year, t.month, t.day, values[0], values[min], 0, 0)
when 5 then t = Time.time_with_datetime_fallback(:local, *values)
when 3 then Date.new(*values)
else nil
end
attributes[name.to_s] = value
end
end
attributes
end
If you find a better solution, please let me know :-)