I have the following validation:
class Metrics
validates :verbs_count, allow_nil: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: ? }
end
I would like to know what would be the maximum value of a float without losing precision. I know it seems like a question that should be easy to find an answer, but I'm not finding it.
As per the docs (which are not in https://api.rubyonrails.org/ pages, but in the code in master):
Validates whether the value of the specified attribute is numeric by
trying to convert it to a float with Kernel.Float (if only_integer
is false) or applying it to the regular expression /\A[\+\-]?\d+\z/
(if only_integer is set to true).
Precision of Kernel.Float values are guaranteed up to 15 digits.
The answer you're looking for is maybe here: "Precision of Kernel.Float values are guaranteed up to 15 digits" - as there's not a maximum number, then you can use whatever you want, just having in mind that greather numbers will loose precision.
Related
I'm looking for a way to simplify the code for the following logic:
Take a value that is either a nil or an empty string
Convert that value to an integer
Set zero values to the maximum value (empty string/nil are converted to 0 when cast as an int)
.clamp the value between a minimum and a maximum
Here's the long form that works:
minimum = 1
maximum = 10_000
value = value.to_i
value = maximum if value.zero?
value = value.clamp(minimum, maximum)
So for example, if value is "", I should get 10,000. If value is "15", I should get 15. If value is "45000", I should get 10000.
Is there a way to shorten this logic, assuming that minimum and maximum are defined and that the default value is the maximum?
The biggest problem I've had in shortening it is that null-coalescing doesn't work on the zero, since Ruby considers zero a truthy value. Otherwise, it could be a one-liner.
you could still do a one-liner with your current logic
minimum, maximum = 1, 10_000
value = ( value.to_i.zero? ? maximum: value.to_i ).clamp(minimum, maximum)
but not sure if your issue is that if you enter '0' you want 1 and not 10_000 if so then try this
minimum, maximum = 1, 10_000
value = (value.to_i if Float(value) rescue maximum).clamp(minimum, maximum)
Consider Fixing the Input Object or Method
If you're messing with String objects when you expect an Integer, you're probably dealing with user input. If that's the case, the problem should really be solved through input validation and/or looping over an input prompt elsewhere in your program rather than trying to perform input transformations inline.
Duck-typing is great, but I suspect you have a broken contract between methods or objects. As a general rule, it's better to fix the source of the mismatch unless you're deliberately wrapping some piece of code that shouldn't be modified. There are a number of possible refactorings and patterns if that's the case.
One such solution is to use a collaborator object or method for information hiding. This enables you to perform your input transformations without complicating your inline logic, and allowing you to access the transformed value as a simple method call such as user_input.value.
Turning a Value into a Collaborator Object
If you are just trying to tighten up your current method you can aim for shorter code, but I'd personally recommend aiming for maintainability instead. Pragmatically, that means sending your value to the constructor of a specialized object, and then asking that object for a result. As a bonus, this allows you to use a default variable assignment to handle nil. Consider the following:
class MaximizeUnsetInputValue
MIN = 1
MAX = 10_000
def initialize value=MAX
#value = value
set_empty_to_max
end
def set_empty_to_max
#value = MAX if #value.to_i.zero?
end
def value
#value.clamp MIN, MAX
end
end
You can easily validate that this handles your various use cases while hiding the implementation details inside the collaborator object's methods. For example:
inputs_and_expected_outputs = [
[0, 10000],
[1, 1],
[10, 10],
[10001, 10000],
[nil, 10000],
['', 10000]
]
inputs_and_expected_outputs.map do |input, expected|
MaximizeUnsetInputValue.new(input).value == expected
end
#=> [true, true, true, true, true, true]
There are certainly other approaches, but this is the one I'd recommend based on your posted code. It isn't shorter, but I think it's readable, maintainable, adaptable, and reusable. Your mileage may vary.
We have some numeric fields, that for legacy reasons have implicit length limits.
Given a number field with a length limit of 5, the obvious route is to set the max to 99999, but then is there any way to specify that 1.111 is ok, and 1.1111 is not, in the swagger spec?
For example:
numberField:
type: number
format: float
minimum: 0
maximum: 99999
Yes, you have to use the property maxLength
numberField:
type: number
format: float
maxLength: 5
I am coding the validation of my app and want to know if there's any difference (security and/or speed) between the use of
length: { minimum: 5, maximum: 30 } and length: 5..30, for example.
Sorry if this had already been asked...
Thank you!
There's no difference in performance because they mean the same thing. The range syntax
length: 5..30
is simply shorthand for an array containing the numbers 5 through 30
length: [5,6,7,...30].to_a
You can also use the minimum and maximum attributes to declare a range.
length: { minimum: 5, maximum: 30 }
Check out the documentation here
http://guides.rubyonrails.org/active_record_validations.html#length
:minimum - The attribute cannot have less than the specified length.
:in (or :within) - The attribute length must be included in a given interval. The value for this option must be a range.
It comes down to preference. The range syntax saves you keystrokes. But there's no difference in performance. You can see for yourself with benchmark.
I'm trying to recalculate percentages in an after_update callback of my model.
def update_percentages
if self.likes_changed? or self.dislikes_changed?
total = self.likes + self.dislikes
self.likes_percent = (self.likes / total) * 100
self.dislikes_percent = (self.dislikes / total) * 100
self.save
end
end
This doesn't work. The percentage always comes out as a 100 or 0, which completely wrecks everything.
Where am I slipping up? I guarantee that self.likes and self.dislikes are being incremented correctly.
The Problem
When you divide an integer by an integer (aka integer division), most programming languages, including Ruby, assume you want your result to be an Integer. This is mostly due to History, because with lower level representations of numbers, an integer is very different than a number with a decimal point, and division with integers is much faster. So your percentage, a number between 0 and 1, has its decimal truncated, and so becomes either 0 or 1. When multiplied by 100, becomes either 0 or 100.
A General Solution
If any of the numbers in the division are not integers, then integer division will not be performed. The alternative is a number with a decimal point. There are several types of numbers like this, but typically they are referred to as floating point numbers, and in Ruby, the most typical floating point number is of the class Float.
1.0.class.ancestors
# => [Float, Precision, Numeric, Comparable, Object, Kernel]
1.class.ancestors
# => [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]
In Rails' models, floats are represented with the Ruby Float class, and decimal with the Ruby BigDecimal class. The difference is that BigDecimals are much more accurate (ie can be used for money).
Typically, you can "typecaste" your number to a float, which means that you will not be doing integer division any more. Then, you can convert it back to an integer after your calculations if necessary.
x = 20 # => 20
y = 30 # => 30
y.to_f # => 30.0
x.class # => Fixnum
y.class # => Fixnum
y.to_f.class # => Float
20 / 30 # => 0
20 / 30.0 # => 0.666666666666667
x / y # => 0
x / y.to_f # => 0.666666666666667
(x / y.to_f).round # => 1
A Solution For You
In your case, assuming you are wanting integer results (ie 42 for 42%) I think the easiest way to do this would be to multiply by 100 before you divide. That pushes your decimal point as far out to the right as it will ever go, before the division, which means that your number is as accurate as it will ever get.
before_save :update_percentages
def update_percentages
total = likes + dislikes
self.likes_percent = 100 * likes / total
self.dislikes_percent = 100 * dislikes / total
end
Notes:
I removed implicit self you only need them on assignment to disambiguate from creating a local variable, and when you have a local variable to disambiguate that you wish to invoke the method rather than reference the variable
As suggested by egarcia, I moved it to a callback that happens before the save (I selected before_save because I don't know why you would need to calculate this percentage on an update but not a create, and I feel like it should happen after you validate that the numbers are correct -- ie within range, and integers or decimal or whatever)
Because it is done before saving, we remove the call to save in the code, that is already going to happen
Because we are not explicitly saving in the callback, we do not risk an infinite loop, and thus do not need to check if the numbers have been updated. We just calculate the percentages every time we save.
Because likes/dislikes is an integer value and integer/integer = integer.
so you can do one of two things, convert to Float or change your order of operations.
self.likes_percent = (self.likes.to_f/total.to_f) * 100
Or, to keep everything integers
self.likes_percent = (self.likes * 100)/total
I'm not sure that this is the only problem that you have, but after_update gets called after the object is saved.
Try changing the update_percentages before - on a before_update or a before_validate instead. Also, remove the self.save line - it will be called automatically later on if you use one of those callbacks.
I am storing a cost in my application. The cost is not formatted in the database. For example: 00.00 saves as 0, 1.00 saves as 1, and 40.50 saves as 40.5
I need to read these values from the database and convert them to strings for dollars and cents. For example: 0 --> cost_dollars = "00" & cost_cents = "00", 1 --> cost_dollars = "01" & cost_cents = "00", 40.5 --> cost_dollars = "40" & cost_cents = "50".
Is there an easy way to do this in ruby on rails? Or does someone have code that does this?
Thanks!
You can accomplish that with this little bit of Ruby code:
fmt = "%05.2f" % cost
cost_dollars, cost_cents = fmt.split '.'
If you're trying to format dollar values in a view, you should look at number_to_currency in ActionView::Helpers::NumberHelper.
>> bd = BigDecimal.new "5.75"
>> include ActionView::Helpers
>> number_to_currency(bd)
=> "$5.75"
As for breaking up the value into separate dollars and cents, my first question would be, "Why?" If you have a good reason, and you're dealing with decimals in your database, then you could do the following.
>> bd = BigDecimal.new "5.75"
>> "dollars:#{bd.truncate} cents:#{bd.modulo(1) * BigDecimal.new('100')}"
=> "dollars:5.0 cents:75.0"
number_to_currency is nice, but it can get expensive; you might want to roll your own if you need to call it a lot.
You should be aware that using a float to store currency can be problematic (and see) if you do a lot of calculations based on these values. One solution is to use integers for currency and count cents. This appears to be the approach used by the money plugin. Another solution is to use a decimal type in your migration, which should work out-of-the-box for modern versions of Rails (> 1.2):
add_column :items, :price, :decimal, :precision => 10, :scale => 2
(:scale is the number of places past the decimal, :precision is the total number of digits.) This will get you BigDecimal objects in Rails, which are a little harder to work with, but not too bad.
Both the integer and decimal approaches are a little slower than floating point. I'm using floats for currency in some places, because I know I won't need to do calculations on the values within Rails, only store and display them. But if you need accurate currency calculations, don't use floats.
Instead of storing as a decimal, store as an integral number of cents. So 1 dollar is stored as 100 in the database.
Alternatively, if you don't mind a bit of performance overhead, check for '.' in the database's value. If it exists, split on '.', and parse the pieces as integers.
sprintf is your friend here:
cost_dollars = sprintf('%02.f', cost)
cost_cents = sprintf('%.2f', cost)