Are there any solutions for translating measurement units on Rails? - ruby-on-rails

I'd like to implement measurement unit preferences in a Ruby on Rails app.
For instance, the user should be able to select between displaying distances in miles or in kilometers. And, obviously, not only displaying, but entering values, too.
I suppose all values should be stored in one global measurement system to simplify calculations.
Are there any drop-in solutions for this? Or should I maybe write my own?

The ruby gem "ruby-units" may help:
http://ruby-units.rubyforge.org/ruby-units/
require 'rubygems'
require 'ruby-units'
'8.4 mi'.to('km') # => 13.3576 km
'8 lb 8 oz'.to('kg') # => 3.85554 kg
a = '3 in'.to_unit
b = Unit('5 cm')
a + b # => 4.968 in
(a + b).to('cm') # => 16.62 cm

You can maybe have a look at this gem, which let's you perform some unit conversions.
Quantity on Github

I built Unitwise to solve most unit conversion and measurement math problems in Ruby.
Simple usage looks like this:
require 'unitwise/ext'
26.2.mile.convert_to('km')
# => #<Unitwise::Measurement 42.164897129794255 kilometer>
If you want store measurements in your Rails models, you could do something like this:
class Race < ActiveRecord::Base
# Convert value to kilometer and store the number
def distance=(value)
super(value.convert_to("kilometer").to_f)
end
# Convert the database value to kilometer measurement when retrieved
def distance
super.convert_to('kilometer')
end
end
# Then you could do
five_k = Race.new(distance: 5)
five_k.distance
# => #<Unitwise::Measurement 5 kilometer>
marathon = Race.new(distance: 26.2.mile)
marathon.distance.convert_to('foot')
# => #<Unitwise::Measurement 138336.27667255333 foot>

Quick search on GitHub turned up this: http://github.com/collectiveidea/measurement
Sounds like it does what you need (as far as converting between units), but I can't say I've used it myself.
Edit: Pierre's gem looks like it's more robust and active.

Related

ActiveRecord - How fast are Calculate() methods in PostgreSQL?

I have a rather noobish question about ActiveRecord in ruby on rails.
I'm working on an app on a Postgresql database that will need to handle large amounts of data from multiple platforms as quickly as possible. I'm going through the process of trying to optimize for speed.
I have two functions and I'm wondering which one would be faster theoretically.
Example #1
def spend_branded(date_range)
total_branded_spend = 0.0
platform_list.each do |platform|
platform.where(date: date_range).each do |platform_performance|
total_branded_spend += platform_performance.spend["branded"].to_f
end
end
total_branded_spend
end
VS.
Example #2
def spend_branded(date_range)
total_branded_spend = 0.0
platform_list.each do |platform|
total_branded_spend += (platform.where(date: date_range).sum(:branded_spend)).to_f
end
total_branded_spend
end
As you can see, in the first example, a selection of records are retrieved via the .where() method and then are iterated on with the desired field summed manually. In the second example however, I'm making use of the .sum() method to do the summing at the database level.
I'm wondering if anyone knows which method is faster in general. I suspect the second method is faster, but is it faster by many degrees?
Thanks so much for taking the time to read this question.
EDIT:
As #lacostenycoder pointed out, I should have clarified what platform_list is. It references an array with 1 to 3 ActiveRecord collections containing 1 record per each day in the date_range.
Upon benchmarking with the method provided in his answer, I found the 2nd method to be slightly faster.
user system total real
spend_branded 0.000000 0.000000 0.000000 ( 0.003632)
spend_branded_sum 0.000000 0.000000 0.000000 ( 0.002612)
(102 records processed)
Here's how you can benchmark your methods for comparison. Open a rails console rails c, then paste this into your console.
def spend_branded(date_range)
total_branded_spend = 0.0
platform_list.each do |platform|
platform.where(date: date_range).each do |platform_performance|
total_branded_spend += platform_performance.spend["branded"].to_f
end
end
total_branded_spend
end
def spend_branded_sum(date_range)
total_branded_spend = 0.0
platform_list.each do |platform|
total_branded_spend += (platform.where(date: date_range).sum(:branded_spend)).to_f
end
total_branded_spend
end
require 'benchmark'
Benchmark.bm do |x|
x.report(:spend_branded) { spend_branded(date_range) }
x.report(:spend_branded_sum) { spend_branded_sum(date_range) }
end
Of course we would expect the 2nd way to be faster. We can probably offer more help if you showed more about the model relations and how platform_list is defined.
Also you might want to check out the PgHero gem which can be helpful in identifying slow queries and where to add indices to get better performance. In general when done correctly, doing proper calculations at the database level will be orders of magnitude faster than iteration over large sets of Ruby object.
Also you might try to refactor your first version to this:
def spend_branded(date_range)
platform_list.map do |platform|
platform.where(date: date_range)
.pluck(:spend).map{|h| h['branded'].to_f}.sum
end.sum
end
And 2nd version to
def spend_branded_sum(date_range)
platform_list.map do |platform|
platform.where(date: date_range).sum(:branded_spend).to_f
end.sum
end
lacostenycoder is correct to recommend that you benchmark your code.
If the values you are trying to sum are directly available in the database, Calculations are very likely going to be faster. I do not know how much faster.
If platform_list is a collection of models, something like this might work and might outperform your iteration:
Platform.
where(date: date_range).
where(id: platform_list.map(&:id)).
sum(:branded_spend)

Unable to match distance computed in geocoder

As per geocoder official doc:
Distance between Eiffel Tower and Empire State Building
Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
=> 3619.77359999382 # in configured units (default miles)
def geodistance
render json: Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
end
In my case I am getting the value to be:
=> 3648.3340765758867
Unit configurations are unchanged, not sure what can go wrong!
irb(main):001:0> require 'geocoder'
true
irb(main):002:0> Geocoder::Calculations.distance_between([47.858205,2.294359], [40.748433,-73.985655])
3648.3340765758867
This one is my sample output.
IMO: Might be that you used different ruby or gem versions in each case or something...
It would be nice to know in which environments did both of this code samples execute.
Use the source, Luke!
EDIT:
Might be something in this part of code:
a = (Math.sin(dlat / 2))**2 + Math.cos(point1[0]) *
(Math.sin(dlon / 2))**2 * Math.cos(point2[0])
c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a))
c * earth_radius(options[:units])
Read me doc distance value :=> 3619.77359999382 is outdated.

Detecting overlapping ranges in Ruby

I have array of ranges :
[[39600..82800], [39600..70200],[70200..80480]]
I need to determine if there is overlapping or not.What is an easy way to do it in ruby?
In the above case the output should be 'Overlapping'.
This is a very interesting puzzle, especially if you care about performances.
If the ranges are just two, it's a fairly simple algorithm, which is also covered in ActiveSupport overlaps? extension.
def ranges_overlap?(r1, r2)
r1.cover?(r2.first) || r2.cover?(r1.first)
end
If you want to compare multiple ranges, it's a fairly interesting algorithm exercise.
You could loop over all the ranges, but you will need to compare each range with all the other possibilities, but this is an algorithm with exponential cost.
A more efficient solution is to order the ranges and execute a binary search, or to use data structures (such as trees) to make possible to compute the overlapping.
This problem is also explained in the Interval tree page. Computing an overlap essentially consists of finding the intersection of the trees.
Is this not a way to do it?
def any_overlapping_ranges(array_of_ranges)
array_of_ranges.sort_by(&:first).each_cons(2).any?{|x,y|x.last>y.first}
end
p any_overlapping_ranges([50..100, 1..51,200..220]) #=> True
Consider this:
class Range
include Comparable
def <=>(other)
self.begin <=> other.begin
end
def self.overlap?(*ranges)
edges = ranges.sort.flat_map { |range| [range.begin, range.end] }
edges != edges.sort.uniq
end
end
Range.overlap?(2..12, 6..36, 42..96) # => true
Notes:
This could take in any number of ranges.
Have a look at the gist with some tests to play with the code.
The code creates a flat array with the start and end of each range.
This array will retain the order if they don't overlap. (Its easier to visualize with some examples than textually explaining why, try it).
For sake of simplicity and readability I'll suggest this approach:
def overlaps?(ranges)
ranges.each_with_index do |range, index|
(index..ranges.size).each do |i|
nextRange = ranges[i] unless index == i
if nextRange and range.to_a & nextRange.to_a
puts "#{range} overlaps with #{nextRange}"
end
end
end
end
r = [(39600..82800), (39600..70200),(70200..80480)]
overlaps?(r)
and the output:
ruby ranges.rb
39600..82800 overlaps with 39600..70200
39600..82800 overlaps with 70200..80480
39600..70200 overlaps with 70200..80480

Generating Random Fixed Decimal in Rails

I'm trying to generate random data in my rails application.
But I am having a problem with decimal amount. I get an error
saying bad value for range.
while $start < $max
$donation = Donation.new(member: Member.all.sample, amount: [BigDecimal('5.00')...BigDecimal('200.00')].sample,
date_give: Random.date_between(:today...Date.civil(2010,9,11)).to_date,
donation_reason: ['tithes','offering','undisclosed','building-fund'].sample )
$donation.save
$start +=1
end
If you want a random decimal between two numbers, sample isn't the way to go. Instead, do something like this:
random_value = (200.0 - 5.0) * rand() + 5
Two other suggestions:
1. if you've implemented this, great, but it doesn't look standard Random.date_between(:today...Date.civil(2010,9,11)).to_date
2. $variable means a global variable in Ruby, so you probably don't want that.
UPDATE --- way to really get random date
require 'date'
def random_date_between(first, second)
number_of_days = (first - second).abs
[first, second].min + rand(number_of_days)
end
random_date_between(Date.today, Date.civil(2010,9,11))
=> #<Date: 2012-05-15 ((2456063j,0s,0n),+0s,2299161j)>
random_date_between(Date.today, Date.civil(2010,9,11))
=> #<Date: 2011-04-13 ((2455665j,0s,0n),+0s,2299161j)>

How to enforce the number of significant digits of a BigDecimal

I defined a decimal field with a scale / significant digits of 4 in mysql (ex. 10.0001 ). ActiveRecord returns it as a BigDecimal.
I can set the field in ActiveRecord with a scale of 5 (ex. 10.00001 ), and save it, which effectively truncates the value (it's stored as 10.0000).
Is there a way to prevent this? I already looked at the BigDecimal class if there is a way to force scale. Couldn't find one. I can calculate the scale of a BigDecimal and return a validation error, but I wonder if there is a nicer way to enforce it.
You could add a before_save handler for your class and include logic to round at your preference, for example:
class MyRecord < ActiveRecord::Base
SCALE = 4
before_save :round_decimal_field
def round_decimal_field
self.decimal_field.round(SCALE, BigDecimal::ROUND_UP)
end
end
r = MyRecord.new(:decimal_field => 10.00009)
r.save!
r.decimal_field # => 10.0001
The scale factor might even be assignable automatically by reading the schema somehow.
See the ROUND_* constant names in the Ruby BigDecimal class documentation for other rounding modes.

Resources