Fields for an athletic 'Race' Time, and converting to a Float - ruby-on-rails

If a Track Athlete was saving their race times after a Track meet...
The athlete selects/enters 'MM:SS:ss' into a field.
I need to save it as a float (of seconds) in the database.
800M: ( '01:48.37' --to-- 108.37 )
100M: ( '00:10.59' --to-- 10.59 )
10,000M: ( '27:08.11' --to-- 1628.11 )
--
I havent found a straightforward or reliable way that allows a User to input that kind of time, then convert the object to a float before_save. Can I make this work, or is there a better way to do do this?
Thanks.

Rails allows you to store datatypes as float. You just have to handle the conversion.
How are you submitting the data? As what type? If you were submitting a string you could just do something like this:
min, sec, centi = "1:23:45".split(":")
seconds = (min.to_i * 60) + sec.to_i + (centi.to_f / 100)

Related

How to solve Mathematical Expressions in Rails 4 like 6000*70%?

I am using Dentaku gem to solve little complex expressions like basic salary is 70% of Gross salary. As the formulas are user editable so I worked on dentaku.
When I write calculator = Dentaku::Calculator.new to initialize and then enter the command calculator.evaluate("60000*70%") then error comes like below:
Dentaku::ParseError: Dentaku::AST::Modulo requires numeric operands
from /Users/sulman/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/dentaku-2.0.8/lib/dentaku/ast/arithmetic.rb:11:in `initialize'
I have array is which formula is stored like: ["EarningItem-5","*","6","7","%"] where EarningItem-5 is an object and has value 60000
How can I resolve such expressions?
For this particular case you can use basic_salary = gross_salary * 0.7
Next you need to create the number field in your views which accepts 0..100 range. At last, set up the after_save callback and use this code:
model
after_create :percent_to_float
protected
def percent_to_float
self.percent = percent / 100.0
self.save
end
edit:
Of course, you can simply use this formula without any callbacks:
basic_salary = gross_salary / 100.0 * 70
where 70 is user defined value.
Dentaku does not appear to support "percent". Try this instead
calculator.evaluate('60000 * 0.7')

Define Money Fomat in Laravel

I am trying to save money format in laravel 5.1.
Here is table price define:
$table->decimal(price,6,2);
For instance ; when 1.000,50 Turkish Liras saving to MySQL this format 1.00
How can solve this issue?
You can try defining your price like this
$table->decimal('price',9,3);
Where,
9 is the precision, ie 1234567.89 has a precision of 9
3 is the number of decimal places, ie 123456.789 has a scale of 3
In other words, if we use less decimal-places than 3, we can use remaining for real-number places.
You can refer to this link for about precision and scale of database
How do I interpret precision and scale of a number in a database?
I would suggest not using a float value to store currency as decimals, since floats don't act exactly as you would expect them to, due to the way they are stored in the system.
You would be much better off storing the value in "kuruş" (the subunit of Turkish Lira), as it will be much, much easier in the long run.
In other words, storing the lowest unit you think will be ever required, like storing Centi-meters instead of Meters (Centi is originally Greekish name for "0.01" number).
Secondly, if you're using Eloquent you can use mutators/accessors on the Model e.g.
public function getPriceAttribute($price)
{
return $price / 100;
}
public function setPriceAttribute($price)
{
$this->attributes['price'] = $price * 100;
}
That way you don't have to manually convert the price.
Update
If you're using Laravel 9 or above, you can use the new Attribute syntax instead:
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function price(): Attribute
{
return Attribute::make(
get: fn ($price) => $price / 100,
set: fn ($price) => $price * 100,
);
}
Illuminate blueprints do not support money columns. AFAIK, the money column type itself is only supported in a couple of DBMSes.
What you can do is issue an ALTER TABLE statement to the database after the initial CREATE statement:
Schema::create('my_table', function(Blueprint $table) {
$table->decimal('my_money_column', 999, 2);
// ... the rest of the column definitions
});
DB::statement("
ALTER TABLE my_table ALTER COLUMN my_money_column
SET DATA TYPE MONEY;
");
Beware, though, as this will (probably) break cross-DBMS compatibility of your migration scripts.

Moving Average across Variables in Stata

I have a panel data set for which I would like to calculate moving averages across years.
Each year is a variable for which there is an observation for each state, and I would like to create a new variable for the average of every three year period.
For example:
P1947=rmean(v1943 v1944 v1945), P1947=rmean(v1944 v1945 v1946)
I figured I should use a foreach loop with the egen command, but I'm not sure about how I should refer to the different variables within the loop.
I'd appreciate any guidance!
This data structure is quite unfit for purpose. Assuming an identifier id you need to reshape, e.g.
reshape long v, i(id) j(year)
tsset id year
Then a moving average is easy. Use tssmooth or just generate, e.g.
gen mave = (L.v + v + F.v)/3
or (better)
gen mave = 0.25 * L.v + 0.5 * v + 0.25 * F.v
More on why your data structure is quite unfit: Not only would calculation of a moving average need a loop (not necessarily involving egen), but you would be creating several new extra variables. Using those in any subsequent analysis would be somewhere between awkward and impossible.
EDIT I'll give a sample loop, while not moving from my stance that it is poor technique. I don't see a reason behind your naming convention whereby P1947 is a mean for 1943-1945; I assume that's just a typo. Let's suppose that we have data for 1913-2012. For means of 3 years, we lose one year at each end.
forval j = 1914/2011 {
local i = `j' - 1
local k = `j' + 1
gen P`j' = (v`i' + v`j' + v`k') / 3
}
That could be written more concisely, at the expense of a flurry of macros within macros. Using unequal weights is easy, as above. The only reason to use egen is that it doesn't give up if there are missings, which the above will do.
FURTHER EDIT
As a matter of completeness, note that it is easy to handle missings without resorting to egen.
The numerator
(v`i' + v`j' + v`k')
generalises to
(cond(missing(v`i'), 0, v`i') + cond(missing(v`j'), 0, v`j') + cond(missing(v`k'), 0, v`k')
and the denominator
3
generalises to
!missing(v`i') + !missing(v`j') + !missing(v`k')
If all values are missing, this reduces to 0/0, or missing. Otherwise, if any value is missing, we add 0 to the numerator and 0 to the denominator, which is the same as ignoring it. Naturally the code is tolerable as above for averages of 3 years, but either for that case or for averaging over more years, we would replace the lines above by a loop, which is what egen does.
There is a user written program that can do that very easily for you. It is called mvsumm and can be found through findit mvsumm
xtset id time
mvsumm observations, stat(mean) win(t) gen(new_variable) end

Generating a unique and random 6 character long string to represent link in ruby

I am generating a unique and random alphanumeric string segment to represent certain links that will be generated by the users. For doing that I was approaching with "uuid" number to ensure it's uniqueness and randomness, but, as per my requirements the string shouldn't be more than 5 characters long. So I dropped that idea.
Then I decided to generate such a string using random function of ruby and current time stamp.
The code for my random string goes like this:-
temp=DateTime.now
temp=temp + rand(DateTime.now.to_i)
temp= hash.abs.to_s(36)
What I did is that I stored the current DateTime in a temp variable and then I generated a random number passing the current datetime as parameter. Then in the second line actually added current datetime and random number together to make a unique and random string.
Soon I found,while I was testing my application in two different machines and send the request at the same time, it generated the same string(Though it's rare) once after more than 100 trials.
Now I'm thinking that I should add one more parameter like mac address or client ip address before passing to_s(36) on temp variable. But can't figure out how to do it and even then whether it will be unique or nor...
Thanks....
SecureRandom in ruby uses process id (if available) and current time. You can use the urlsafe_base64(n= 16) class method to generate the sequence you need. According to your requirements I think this is your best bet.
Edit: After a bit of testing, I still think that this approach will generate non-unique keys. The way I solved this problem for barcode generation was:
barcode= barcode_sql_id_hash("#{sql_id}#{keyword}")
Here, your keyword can be time + pid.
If you are certain that you will never need more than a given M amount of unique values, and you don't need more than rudimentary protection against guessing the next generated id, you can use a Linear Congruentual Generator to generate your identificators. All you have to do is remember the last id generated, and use that to generate a new one using the following formula:
newid = (A * oldid + B) mod M
If 2³² distinct id values are enough to suit your needs, try:
def generate_id
if #lcg
#lcg = (1664525 * #lcg + 1013904223) % (2**32)
else
#lcg = rand(2**32) # Random seed
end
end
Now just pick a suitable set of characters to represent the id in as little as 6 character. Uppercase and lowercase letters should do the trick, since (26+26)^6 > 2^32:
ENCODE_CHARS = [*?a..?z, *?A..?Z]
def encode(n)
6.times.map { |i|
n, mod = n.divmod(ENCODE_CHARS.size)
ENCODE_CHARS[mod]
}.join
end
Example:
> 10.times { n = generate_id ; puts "%10d = %s" % [n, encode(n)] }
2574974483 = dyhjOg
3636751446 = QxyuDj
368621501 = bBGvYa
1689949688 = yuTgxe
1457610999 = NqzsRd
3936504298 = MPpusk
133820481 = PQLpsa
2956135596 = yvXpOh
3269402651 = VFUhFi
724653758 = knLfVb
Due to the nature of the LCG, the generated id will not repeat until all 2³² values have been used exactly once each.
There is no way you can generate a unique UUID with only five chars, with chars and numbers you have a basic space of around 56 chars, so there is a max of 56^5 combinations , aprox 551 million (Around 2^29).
If with this scheme you were about to generate 10.000 UUIDs (A very low number of UUIDs) you would have a probability of 1/5.000 of generating a collision.
When using crypto, the standard definition of a big enough space to avert collisions is around 2^80.
To put this into perspective, your algorithm would be better off if it generated just a random integer (a 32 bit uint is 2^32, 8 times the size you are proposing) which is clearly a bad idea.

Ruby: Math functions for Time Values

How do I add/subtract/etc. time values in Ruby? For example, how would I add the following times?
00:00:59 + 00:01:43 + 00:20:15 = ?
Use ActiveSupport, which has a ton of built-in date extensions.
require 'active_support/core_ext'
t1 = "#{Date.today} 00:00:59".to_time
t2 = "#{Date.today} 00:01:43".to_time
t3 = "#{Date.today} 00:20:15".to_time
t1.since(t2.seconds_since_midnight+t3.seconds_since_midnight)
or, if you don't care about the date, only time:
t1.since(t2.seconds_since_midnight+t3.seconds_since_midnight).strftime("%H:%M:%S")
For a full list, check out http://guides.rubyonrails.org/active_support_core_extensions.html#extensions-to-date
Kind of ugly, but you could use DateTime.parse(each_interval) & calculate the number of seconds in each. Like this:
require 'date'
def calc_seconds(time_string)
date_time = DateTime.parse(time_string)
hour_part = date_time.hour * 60 * 60
minute_part = date_time.minute * 60
second_part = date_time.second
hour_part + minute_part + second_part
end
...which gives you your result in seconds, assuming valid inputs. At which point you can add them together.
You could reverse the process to get the interval in your original notation.
I really think there ought to be an easier method, but I don't know of one.
One way would be to convert everything to seconds and then performing the operations... Then you would need to convert it again to a time object with
Time.at(seconds_result).strftime('%H:%M:%S')
And you would get the time nicely formatted (as a string).
I am trying to find a gem that does this, and other operations.
You probably want to use a gem that does not concern itself with the actual day. You could perform acrobatics using DateTime and or Time, but you would constantly be battling how to handle days.
One gem that may be useful is tod (TimeOfDay), https://github.com/JackC/tod
With that you could directly do TimeOfDay.parse "00:01:43", add the values, and print the result using strftime("%H:%M:%S").

Resources