Rails validate type date? - ruby-on-rails

I think i might be dreaming, but i think i read somewhere that you can validate the type of an attribute of an object before you save it? Something
like validates :transaction_date, :type => Date and that will make sure that its a date?
Is this possible in Rails 3.2? i am trying to find evidence of this on the net. i have already looked here at the rails api and i am going through the ActiveRecord support.

As a complement to the other answers, note that you can define a custom validator to let you use exactly the syntax you proposed:
validates :transaction_date, :type => Date
as follows:
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
value.class == options[:with]
end
end
Of course, if you want to allow subclasses, you could change the test to use kind_of?.

Rails doesn't support this directly; the closest it comes is probably validates_format_of, or supplying your own custom validator.
I think what you want is the validates_timeliness gem. It not only validates that something is a valid date, but you can specify whether it should be before or after today and various other date range checks.

You can use following gem:
https://github.com/codegram/date_validator
It contains a date validator. You can add a few options.

A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :transaction_date, :date
end
This will throw an exception when anything except a a Date is assigned to :transaction_date.

Here's another custom validator that uses is_a? instead of class, and therefore is a bit less strict and handles things such as Numeric:
class IsAValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, (options[:message] || "is not a #{options[:with]}") unless
value.is_a?(options[:with])
end
end
Usage:
validates :number, is_a: Numeric

Related

Checking if attribute_changed? in ActiveModel::EachValidator

I have a class Person that has first_name, middle_name, last_name.
I have a customed Each validator for these 3 attributes like so
validates :first_name, :middle_name, :last_name, nameValidator: true
In this validator I want to check if any one of these 3 attributes changed and given a few more conditions I'll validate name. For that I'm trying attribute_changed? but it doesn't work.
I've checked different methods from ActiveModel::Dirty and Activerecod::Dirty but nothing seems to work to check changes in each attribute. What am I missing?
module Person
class nameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless can_normalize?(record)
#Normalization code
end
def can_normalize?(record)
anything_new = record.new_record? || attribute_changed?
end
end
end
If you need to check that some attribute was changed, you need to call attribute_changed? method on the record and pass this attribute like this
return unless record.new_record? || record.attribute_changed?(attribute)
Or may be use metaprogramming method like this
return unless record.new_record? || record.public_send("#{attribute}_changed?")
Some notes:
In Ruby we usually use PascalCase for class names and snake_case for hash keys
Validations are used for data validation, not for some normalization. It is usually used to add validation errors

Rails 5 re-use before_validation code in multiple models with different fields

I have a Rails 5 application where users can enter currency values in different fields and different models.
Since I only serve one locale, I want users to be able to enter decimals using both . and , decimal separators and ignore any thousands separator.
For example, my users might enter: 1023.45 or 1023,45 but never 1.023,45.
Simple solution that fits my use case: On specific decimal fields representing currency, replace , with . using gsub(',', '.').
What is the best place to put this code? There are multiple models with differently named fields that need to use this code.
Preferably, I would use something like a custom validator that I create once and simply reference with 1 line from all models with the appropriate field. Very much like this example of a custom validator that checks whether an entered value is positive:
class PositiveValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value and BigDecimal.new(value).negative?
record.errors[attribute] << 'is negative'
end
end
end
And then in the model reference it with:
validates :total, positive: true, allow_blank: true
However, this is ofcourse for validations, which shouldn't modify data. I tried to do this with concerns, but from the examples I have seen I should already know which fields are being transformed.
Do I really need to write in each model something like this:
before_validation lambda {self.total.gsub!(',', '.') if self.total}
or is there a more elegant Rails-like solution?
edit: again, note that field name 'total' might be different in each model
You could use custom type-casting starting from Rails 4.2
class LocalFloat < ActiveRecord::Type::Float
def cast_value(value)
value = value.gsub(',', '.') if value.is_a? String
super(value)
end
end
class Order < ApplicationRecord
attribute :total, LocalFloat.new
end
o = Order.new total: '5,5'
o.total # => 5.5

How do I force a rails ActiveRecord string column to only accept strings?

I have a Post class with a title:string field
post = Post.create!
#=> Post created
post.title = "foo"
post.save!
# success
post.title = 1
post.save!
# succeeds, but I want it to throw a type mismatch exception
How do I get the last save! to throw a type mismatch?
This really runs against the grain of what Ruby is all about. If it's a string column, the database layer will take care of rendering that value as a string and saving it.
The concept of a type is different in Ruby than in other languages. Generally if an object supports to_s then it's considered to be string-enough to use, the basic tenet of Duck Typing.
If you pass in something that can't be rendered as a string you will get an exception. For example:
post.title = Post.find(5)
If you're looking to ensure your fields are not numerical, you should add a validation that enforces a particular format.
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer more of the "why" it was created in the first place.
With this library your code would simple be:
class Post < ActiveRecord::Base
validates_type :title, :string
end
This will throw an exception when an integer is assigned to title instead of quietly casting the title to a string and saving it.
Ruby is a duck-typed language and doesn't have type mismatches. If an object responds to to_s, Rails will call to_s on it and put it into a string field.
It sounds like what you really want is to validate the format of the value you're putting into the field (is the string "2" a valid value?), which is what validates_format_of is for. For example, if you want to ensure that the value has at least one non-digit character, you could do this:
class Post < ActiveRecord::Base
validates_format_of :title, with: /\D/
end
Of course, you very well may want to customize that regex a bit more.
If you really can't stand duck-typing and want to make sure nothing but a true String goes into that column, you can override title=:
class Post < ActiveRecord::Base
def title=(value)
throw "`title' must be a String" unless value.is_a?(String)
super
end
end
Note that this won't be invoked when you do e.g. post[:title] = ... or post.write_attribute(:title, ...) but it should cover 95% of situations.
To answer the exact question, if you want to make sure that only objects of class String are saved to the field, you should add the following custom validation.
validate :must_be_string
def must_be_string
errors.add(:base, 'The field must be String') unless title.class == String
end
You can define a custom validator.
validates :title, type: String
and then:
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
value.class == options[:with]
end
end

Is there way to validate emails on signup based on the domain in Rails?

Example:
Accepted extensions: "#blogsllc.org"
A user signs up with the email "joe#blogsllc.org" would be able to create an account.
Wondering what would be the best way to do this in Rails and how others would approach this? I imagined trying to check the format of the email address against a bunch of regular expressions but this could be tedious as the list of supported extensions grow.
The other way to do this would be to have a database of the supported extensions and check the created email address against the database to see if the extension is accepted but I'm not sure what would be the best way to implement this in Rails.
I'm looking to implement something similar to what Facebook did in it's early days.
Any help appreciated.
EDIT for misunderstanding:
If you don't need anything more fancy than a straight-up match of the domain (files have extensions, emails have domains), just splitting on # and matching the second part with a database column is the easiest way.
You can add the following code
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value =~ /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
record.errors[attribute] << (options[:message] || "is not an email")
end
end
end
class Person < ActiveRecord::Base
validates :email, :presence => true, :email => true
end
This Link will be useful

Rails: how to require at least one field not to be blank

I know I can require a field by adding validates_presence_of :field to the model. However, how do I require at least one field to be mandatory, while not requiring any particular field?
thanks in advance
-- Deb
You can use:
validate :any_present?
def any_present?
if %w(field1 field2 field3).all?{|attr| self[attr].blank?}
errors.add :base, "Error message"
end
end
EDIT: updated from original answer for Rails 3+ as per comment.
But you have to provide field names manually.
You could get all content columns of a model with Model.content_columns.map(&:name), but it will include created_at and updated_at columns too, and that is probably not what you want.
Here's a reusable version:
class AnyPresenceValidator < ActiveModel::Validator
def validate(record)
unless options[:fields].any?{|attr| record[attr].present?}
record.errors.add(:base, :blank)
end
end
end
You can use it in your model with:
validates_with AnyPresenceValidator, fields: %w(field1 field2 field3)
Add a validate method to your model:
def validate
if field1.blank? and field2.blank? and field3.blank? # ...
errors.add_to_base("You must fill in at least one field")
end
end
I believe something like the following may work
class MyModel < ActiveRecord::Base
validate do |my_model|
my_model.my_validation
end
def my_validation
errors.add_to_base("Your error message") if self.blank?
#or self.attributes.blank? - not sure
end
end
Going further with #Votya's correct answer, here is a way to retrieve all columns besides created_at and updated_at (and optionally, any others you want to throw out):
# Get all column names as an array and reject the ones we don't want
Model.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}
For example:
1.9.3p327 :012 > Client.content_columns.map(&:name).reject {|i| i =~ /(created|updated)_at/}
=> ["primary_email", "name"]
If you only have two fields, this will get the job done:
validates :first_name, presence: true, if: :nick_name.blank?
validates :nick_name, presence: true, if: :first_name.blank?
This does not scale up well with more fields, but when you only have two, this is perhaps clearer than a custom validation method.
n.b. If they omit both, the error message will appear more restrictive than you intend. (e.g. First Name is required. Nick Name is required.) ¯\(ツ)/¯

Resources