How do I validate coordinates in my Rails model? - ruby-on-rails

I have built a Rails app that uses the HTML5 Geolocation API to get a user's current position. I use the coordinates to populate a field in my form. I want to know how to validate this in my model. Here is what my form input looks like:
<%= f.input :start_point, label: false, input_html: { id: 'coordinatesStart' } %>
Coordinates should be i the form 54.678, 45.789 and can take any amount of numbers after the decimal point.
Any ideas how I could do this?

I can't run it now but try:
validates :points, format: { with: /\d{1,}\.\d{1,},\s\d{1,}\.\d{1,}/, message: "please enter co-ordinates in correct format"}
The logic being:
Match at least 1 number, followed by a decimal, followed by 1 or more numbers, followed by a comma, followed by a space, followed by one or more numbers, followed by a a decimal, followed by one or more numbers

The latitude must be a number between -90 and 90 and the longitude between -180 and 180.
So in your model:
GEOCOORDINATES_REGEX = /\A-?(?:90(?:(?:\.0*)?)|(?:[0-9]|[1-8][0-9])(?:(?:\.\d*)?)),\s-?(?:180(?:(?:\.0*)?)|(?:[0-9]|[1-9][0-9]|1[0-7][0-9])(?:(?:\.\d*)?))\z/.freeze
validates :start_point, format: { with: GEOCOORDINATES_REGEX }

First thing should be to separate input validation from model stuff. Use some separate layer, even if it's just a bare class implementing ActiveModel::Validations.
Then, add a stand-alone validator class like:
class GeoPairValidator < ActiveModel::EachValidator
RE = /\A\d+\.\d+, ?\d+\.\d+\z/
def validate_each(record, attribute, value)
# value should be "nn.nnnnnnn, nn.nnnnnnn"
record.errors.add(attribute, :invalid, value: value) if value.to_s !~ RE
# split by comma, convert to float
v1, v2 = value.to_s.split(', ').map(&:to_f)
# check logical bounds
record.errors.add(attribute, :invalid, value: value) if v1.abs > 90
record.errors.add(attribute, :invalid, value: value) if v2.abs > 180
end
end
Finally, use it in your input class:
class YourInput
include ActiveModel::Validations
attr_accessor :start_point
validates :start_point, geo_point: true
end
Also check https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates for more detailed information.

Related

Handle decimal entered as whole number Rails

I'm fairly new to rails and having a problem that I'm sure is not unique, but can't seem to find an elegant solution to on my own:
I have a model with a :tax_rate decimal attribute that is used to create a total later on using some multiplication in the model.
This works just fine if the user enters the tax rate as '0.09' in the tax rate field, but obviously creates an erroniously large amount when the user enters '9'.
I'm looking to create some code that will handle a whole number being entered into my form.
My form:
<td><%= f.input :tax_rate, label: false, input_html: { class: 'taxtarget', precision: 2 }%></td>
The calcualtions in the model:
before_save :persist_calculations
validates_presence_of :price, :units
def calculated_total
price * units + calculated_tax
end
def calculated_tax
if self.tax_rate.present?
self.price * self.tax_rate
else
self.price
end
end
def persist_calculations
self.total = calculated_total
self.tax_amount = calculated_tax
end
I would like to either handle the whole number by preventing a whole number (requiring a decimal point) or by converting a whole number into a decimal by adding two decimal places to it.
Anyone have any good resources or ideas for how to accomplish this?
Either add a validation to your model for tax_rate so that it cannot be a larger than 1.0:
validates :tax_rate, less_than: 1.0
and/or add a before_validation to your model that converts large numbers to small ones:
before_validation ->(obj) {
obj.tax_rate = obj.tax_rate / 100 if obj.tax_rate > 1
}
and/or handle it your controller, which is generally preferable to using callbacks.

How do I use the value of an attribute within a model? Ruby on Rails

Basically, I have a model, Degree, and it has three attributes: degree_type, awarded_by, and date_awarded.
There are two arrays of values that should be valid for awarded_by. The two valid values for degree_type are "one" and "two", and the valid values for awarded_by depend on "one" and "two".
If degree_type is "one" (has a value of "one", that a user would put in), I want the valid values for awarded_by to be array_one. If degree_type has a value of "two", I want the valid values for awarded_by to be array_two.
Here is the code so far:
class Degree < ActiveRecord::Base
extend School
validates :degree_type, presence: true,
inclusion: { in: ["one",
"two"],
message: "is not a valid degree type"
}
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(awarded_by_type) }
end
Degree.schools outputs an array depending on the degree type, so Degree.schools("one") would return array_one, where
array_one = ['school01', 'school02'...]
My problem is, I don't know how to access the value of degree_type within the model.
What I tried below doesn't work:
validates :awarded_by, presence: true,
inclusion: { in: Degree.schools(:degree_type) }
I tried using before_type_cast but I was either using it incorrectly or there was another problem, as I couldn't get that to work either.
When I test this I get:
An object with the method #include? or a proc, lambda or symbol is required, and must be supplied as the :in (or :within) option of the configuration hash
Help me out? :) If any more info is needed, let me know.
EDIT: To add to this, I double checked it wasn't my Degree.schools method acting up - if I go into the rails console and try Degree.schools("one") or Degree.schools("two") I do get the array I should get. :)
EDIT again: When I tried #Jordan's answer, I got errors in the cases where the awarded_by was incorrect because in those cases, valid_awarded_by_values was nil and there is no include? method for a nil object. Therefore I added an if statement checking for whether valid_awarded_by_values was nil or not (so as to return if it was), and that solved the problem!
I put this inside the method, before the unless statement and after the valid_awarded_by_values declaration:
if valid_awarded_by_values.nil?
error_msg = "is not a valid awarded_by"
errors.add(:awarded_by, error_msg)
return
end
The easiest way will be to write a custom validation method, as described in the Active Record Validations Rails Guide.
In your case, it might look something like this:
class Degree < ActiveRecord::Base
validate :validate_awarded_by_inclusion_dependent_on_degree_type
# ...
def validate_awarded_by_inclusion_dependent_on_degree_type
valid_awarded_by_values = Degree.schools(degree_type)
unless valid_awarded_by_values.include?(awarded_by)
error_msg = "must be " << valid_awarded_by_values.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
errors.add(:awarded_by, error_msg)
end
end
end

Rails 4. Country validation in model

I'm creating rails API and want to want to add validation for countries field which contains an ISO 3166-1 code on model level.
For example if use gem carmen-rails, it provides only helper country_select. Is that way to use validation for accordance country for ISO 3166-1 code in the model?
Here is the newest syntax for validation with the countries gem:
validates :country, inclusion: { in: ISO3166::Country.all.map(&:alpha2) }
You are just trying to validate that the country code entered is appropriate? this should work with carmen
validates :country, inclusion: { in: Carmen::Country.all.map(&:code) }
But if this is all you need seems like the countries gem might work well too. With countries you could do
validates :country, inclusion: { in: Country.all.map(&:pop) }
Or
validate :country_is_iso_compliant
def country_is_iso_compliant
errors.add(:country, "must be 2 characters (ISO 3166-1).") unless Country[country]
end
Update
For Region and State you can validate all 3 at the same time like this.
validates :country, :region, :state, presence: true
validate :location
def location
current_country = Country[country]
if current_country
#valid regions would be something Like "Europe" or "Americas" or "Africa" etc.
errors.add(:region, "incorrect region for country #{current_country.name}.") unless current_country.region == region
#this will work for short codes like "CA" or "01" etc.
#for named states use current_country.states.map{ |k,v| v["name"}.include?(state)
#which would work for "California" Or "Lusaka"(it's in Zambia learn something new every day)
errors.add(:state, "incorrect state for country #{current_country.name}.") unless current_country.states.keys.include?(state)
else
errors.add(:country, "must be a 2 character country representation (ISO 3166-1).")
end
end
Although Region seems unnecessary as you could imply this from the country like
before_validation {|record| record.region = Country[country].region if Country[country]}
Create a Fixture with the data provided by Wikipedia on ISO-3166-1 and validate the country based on that data.
Also you can create an auto-complete feature easing the input. You can look at the auto-complete provided here for guidance.

Ruby Regex for price

This one fails when a zero is at the end
12.12 passes
5.51 passes
12.50 fails
12.60 fails
price_regex = /^\d+(\.\d{2})?$/
why? and how do I fix it?
Some more info
in _form.html.erb
<p>
<%= f.label :price %><br />
<%= f.text_field :price %>
</p>
in menu_item.rb
price_regex = /^\d+(\.\d{2})?$/
validates :price, :presence => true,
:format => { :with => price_regex }
in menu_items_controller.rb
def create
#menu_item = MenuItem.new(params[:menu_item])
if #menu_item.save
respond_with #menu_item, :location => menu_items_url
else
flash[:notice] = "Not Saved"
end
end
price is a decimal in the database with a precision of 2.
You say that price is "a decimal in the database with a precision of 2". That means that price is being represented as a BigDecimal in Ruby and the regex test will be done on the string form of that BigDecimal. A little bit of experimentation will clarify things:
> p = BigDecimal.new('12.50')
=> #<BigDecimal:12a579e98,'0.125E2',18(18)>
> p.to_s
=> "12.5"
And so your regex will fail. You shouldn't be using a regex for this at all, regexes are meant for strings but you're checking a number. You should be able to keep using your regex if you allow for the conversion:
/^\d+(\.\d{1,2})?$/
I'm using Rails 3 with the client_side_validations gem, which means I need a Regexp that works both in Ruby and Javascript. I also have a clear delineation between frontend and backend format--The user should never be able to enter "$12.5", but once it hits the server, I don't care about the trailing 0.
My solution was to add a core extension (in my case, for Float, but BigDecimal would probably be more appropriate in most cases):
class Float
def can_convert_to_i_with_no_loss_of_precision
(self % 1).zero?
end
alias_method :to_s_with_loss_of_trailing_zeroes, :to_s
def to_s
if can_convert_to_i_with_no_loss_of_precision
to_i.to_s
else
"#{to_s_with_loss_of_trailing_zeroes}#{0 if (self * 10 % 1).zero?}"
end
end
end
Now I can use this in a Model, and it plays nicely on the front end (Javascript doesn't convert it to a Float, so the user will always be forced to enter 2 digits after the decimal) and on the backend (where ActiveModel's FormatValidator will call to_s and the core extension will know when to add the trailing 0):
validates :price, :format => { :with => /^\d+(\.\d{2})?$/, :allow_blank => true }
The regex looks fine to me. I tested it at Rubular with the inputs you mentioned and a few more, and it captures all of them correctly.
The problem is likely with some other part of the code. Maybe you are using price = <regex>, whereas you should be using price =~ <regex> to match a string with a regex.

overriding attribute function rails mongoid

I have a model like this.
class Money
include Mongoid::Document
#interval is how often the compensation is paid
field :salary, :type => Integer # must be saved in cents
field :commission, :type => Integer # must be saved in cents
field :total, :type => Integer # must be saved in cents
end
total is sum of salary and commission. salary and commission both are saved in cents.
But my problem is that when it is edited i need to show it in dollar figure.
For example, if salary in cent is 5000000 then when i press edit i need to see 50000 in the salary textbox.
Some other solutions are also welcomed
If you want to enforce this pattern at the model level then you could override the setters and getters:
class Money
#...
def salary
self.salary / 100
end
def salary=(value)
self.salary * 100
end
end
In this case you'll have the editing/displaying for free, without writing any helpers.
Although, I think the proper way for doing it is at the view level through a helper definition. The model should not be concerned with this.
Look at ActionView::Helpers::NumberHelper. In your case you could write your own helper like this:
def money_to_textbox (money)
money / 100
end
This helper method should be placed in app\helpers and then in a view you can use like this:
<%= money_to_textbox #money %>

Resources