I wanted to validate class attributes with respect to their data type.
I am writing an API in ruby and i need to check if a particular parameter data is of type String, hash, Array etc.
eg.
we have
class ApiValidation
include ActiveModel::Validations
attr_accessor :fieldName
validates :fieldName, :presence => true
end
so similar to :presence => true, i want to add one more validation like :datatype => :string
Please suggest me some custom validation or any other approach to achieve my requirement.
You can check out dry-rb stack.
There is https://github.com/dry-rb/dry-types (https://dry-rb.org/gems/dry-types/1.2/) gem which do exactly what you want.
From docs:
Strict types will raise an error if passed an attribute of the wrong type:
class User < Dry::Struct
attribute :name, Types::Strict::String
attribute :age, Types::Strict::Integer
end
User.new(name: 'Bob', age: '18')
# => Dry::Struct::Error: [User.new] "18" (String) has invalid type for :age
In Ruby, "datatypes" are actually just classes. So, all you really have to do is check which class fieldName belongs to (fieldName.class) in your custom validation.
Parameters are always received as text. Ruby doesn't have static types, but it does have classes, so your code needs to parse each attribute and convert them into the class you expect.
This could be as simple as riding on Ruby's inbuilt conversion methods, eg. to_i, to_sym, to_d, to_h, or you might need to add more logic, eg. JSON.parse.
Do note the difference between strict and non-strict parsing which will require different control flow handling.
>> "1one".to_i
=> 1
>> Integer("1one")
ArgumentError: invalid value for Integer(): "1one"
According to the Rails Guides and Rails API docs, we could use validates_each method which is available in ActiveModel::Validations.
For example:
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, " must be a string." unless value.is_a? String
end
end
Related
Is it possible to decide which type casts an attribute in ActiveModel::Attributes on runtime? I have the following code
class DynamicType < ActiveModel::Type::Value
def cast(value)
value # here I don't have access to the underlying instance of MyModel
end
end
class MyModel
include ActiveModel::Model
include ActiveModel::Attributes
attribute :value, DynamicType.new
attribute :type, :string
end
MyModel.new(type: "Integer", value: "12.23").value
I'd like to decide based on the assigned value of type how to cast the value attribute. I tried it with a custom type, but it turns out, inside the #cast method you don't have access to the underlying instance of MyModel (which also could be a good thing, thinking of separation of concerns)
I also tried to use a lambda block, assuming that maybe ActiveModel see's an object that responds to #call and calls this block on runtime (it doesn't):
class MyModel
include ActiveModel::Model
include ActiveModel::Attributes
attribute :value, ->(my_model) {
if my_model.type == "Integer"
# some casting logic
end
}
attribute :type, :string
end
# => throws error
# /usr/local/bundle/gems/activemodel-5.2.5/lib/active_model/attribute.rb:71:in `with_value_from_user': undefined method `assert_valid_value' for #<Proc:0x000056202c03cff8 foo.rb:15 (lambda)> (NoMethodError)
Background: type comes from a DB field and can have various classes in it that do far more than just casting an Integer.
I could just do a def value and build the custom logic there, but I need this multiple times and I also use other ActiveModel features like validations, nested attributes... so I would have to take care about the integration myself.
So maybe there is a way to do this with ActiveModel itself.
Your are storing the value as a string in the DB, so ActiveRecord will retreive it as a string. You will need to convert it manually. Rails provides these methods, that I am sure you are familiar with:
"1".to_i => to integer => 1
"foo.to_sym => to symbol => :foo
"1".to_f => to float => "1.0"
123.to_s => to string => "123"
(1..10).to_a => to array => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Then in your model you could do:
def value
case type
when "Integer"
value.to_i
when "Float"
value.to_f
...
end
This could work but I see a problem with this approach. You decalre an attribute with the name type, you could get some erros of Rails complaining that type is a reserve word for STI, or get weird bugs, I suggest you to rename it.
I'm trying to implement to validations on a given model array-like field, using the Enumerize Gem. I want to:
validate that all the elements of a list belong to a given subset, using Enumerize
validate that the list is not empty (using validates :field, presence: true)
It seems that when I provide a list containing an empty string, the presence validator fails. See this example.
class MyModel
include ActiveModel::Model
extend Enumerize
enumerize :cities, in: %w(Boston London Berlin Paris), multiple: true
validates :cities, presence: true
end
# Does not behave as expected
a = MyModel.new(cities: [""])
a.cities.present? # => false
a.valid? # => true, while it should be false.
It seems to work in some other cases (for instance when you provide a non empty string that is not in the Enum). For instance
# Behaves as expected
a = MyModel.new(cities: ["Dublin"])
a.cities.present? # => false
a.valid? # => false
Is there a workaround available to be able to use both Enumerize validation and ActiveModel presence validation?
Thanks!
The enumerize gem is saving your multiple values as an string array. Something like this: "[\"Boston\"]". So, with an empty array you have: "[]". The presencevalidator uses blank? method to check if the value is present or not. "[]".blank? returns false obviously.
So, you can try some alternatives:
Option 1:
validates :cities, inclusion: { in: %w(Boston London Berlin Paris) }
Option 2:
Add a custom validator
validate :ensure_valid_cities
def ensure_valid_cities
errors.add(:cities, "invalid") unless cities.values.empty?
end
This is actually a bug that should be fixed in a later version of Enumerize (See https://github.com/brainspec/enumerize/pull/226).
In the meantime, you can use a custom validator as a workaround (See Leantraxx's answer).
Let's say that I have an input field with a value, and I want to validate it (on the server side) to make sure, for instance, that the field has at least 5 characters.
The problem is that it is not something that I want to save in the database, or build a model. I just want to check that the value validates.
In PHP with Laravel, validation is quite easy:
$validator = Validator::make($data, [
'email' => ['required', 'email'],
'message' => ['required']]);
if ($validator->fails()) { // Handle it... }
Is there anything similar in Rails, without need of ActiveRecord, or ActiveModel? Not every data sent from a form makes sense as a Model.
You can use ActiveModel::Validations like this
class MyClass
include ActiveModel::Validations
validates :email, presence: true
validates :message, presence: true
end
It will act as a normal model and you will be able to do my_object.valid? and my_object.errors.
Rails validations live in ActiveModel so doing it without ActiveModel seems kind of counter-productive. Now, if you can loosen that requirement a bit, it is definitely possible.
What I read you asking for, and as I read the PHP code doing, is a validator-object that can be configured on the fly.
We can for example build a validator class dynamically and use instance of that class to run our validations. I have opted for an API that looks similar to the PHP one here:
class DataValidator
def self.make(data, validations)
Class.new do
include ActiveModel::Validations
attr_reader(*validations.keys)
validations.each do |attribute, attribute_validations|
validates attribute, attribute_validations
end
def self.model_name
ActiveModel::Name.new(self, nil, "DataValidator::Validator")
end
def initialize(data)
data.each do |key, value|
self.instance_variable_set("##{key.to_sym}", value)
end
end
end.new(data)
end
end
Using DataValidator.make we can now build instances of classes with the specific validations that we need. For example in a controller:
validator = DataValidator.make(
params,
{
:email => {:presence => true},
:name => {:presence => true}
}
)
if validator.valid?
# Success
else
# Error
end
I have a form where i pass a field named :type and i want to check if it's value is inside an array of allowed types so that no one is allowed to post not-allowed types.
the array looks like
#allowed_types = [
'type1',
'type2',
'type3',
'type4',
'type5',
'type6',
'type7',
etc...
]
i have tried using validates_exclusion_of or validates_inclusion_of but it doesn't seem to work
first, change the attribute from type to something else, type is a reserved attrubute name use for Single Table Inheritance and such.
class Thing < ActiveRecord::Base
validates :mytype, :inclusion=> { :in => #allowed_types }
ActiveModel::Validations provides a helper method for this. An example call would be:
validates_inclusion_of :type, in: #allowed_types
ActiveRecord::Base is already a ActiveModel::Validations, so there is no need to include anything.
http://apidock.com/rails/ActiveModel/Validations/HelperMethods/validates_inclusion_of
Also, #RadBrad is correct that you should not use type as a column name as it is reserved for STI.
Just for those lazy people (like me) to copy the latest syntax:
validates :status, inclusion: %w[pending processing succeeded failed]
validates_inclusion_of is out of date since Rails 3.
:inclusion=> hash syntax is out of date since Ruby 2.0.
In favor of %w for word array as the default Rubocop option.
With variations:
Default types as a constant:
STATUSES = %w[pending processing succeeded failed]
validates :status, inclusion: STATUSES
OP's original:
validates :mytype, inclusion: #allowed_types
Apologies if this is a really common and/or ridiculous question; I swear I've read over the documentation multiple times and everything seems so focused on ActiveRecord to the point they've wandered off the path of forms that do something other than create or edit model data.
Take for example a form with inputs to control the extraction and display of some statistics. What does rails provide me with for validating the user input of this form, which won't be invoking save on any records? Things like:
:email must be an email address
:num_products must be a positive whole number
:gender must be one of "M" or "F"
:temperature must be between -10 and 120
Etc etc (the sort of stuff that comes standard in most web frameworks)...
Is there something in Rails for me to perform this arbitrary validation and some view helper to display a list of errors, or is everything coupled with ActiveRecord?
Apologies if I've overlooked this in the documentation, but this and this don't really cover it, at least as far as mt weary eyes can tell.
scratches head
Thanks to Rob's answer, here's what I've come up with. I created a utility class (aptly named Validator) which is just embedded into my controllers for anything that needs it.
module MyApp
class Validator
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
def initialize(attributes = {})
super
attributes.each { |n, v| send("#{n}=", v) if respond_to?("#{n}=") }
end
end
end
Now in the controller, for example, just define a little inline-class:
class LoginController < ApplicationController
class AuthenticationValidator < MyApp::Validator
attr_accessor :email
attr_accessor :password
validates_presence_of :email, :message => "E-mail is a required field"
validates_presence_of :password, :message => "Password cannot be blank"
end
def authenticate
if request.post?
#validator = AuthenticationValidator.new(params)
if #validator.valid?
# Do something meaningful
end
end
end
It feels a bit unnecessary to stick every single set of validation rules in their own .rb when the logic is more controller-oriented IMHO. There is probably a more succinct way to write this, but I'm pretty new to Ruby and Rails.
Yes, it can be done fairly easily.
You can use the validations API for it.
As an example, here is a contact us model that I use for an application that is not using ActiveRecord.
class ContactUs
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :message
validates_presence_of :name, :email, :message, :subject
validates_format_of :email, :with => /\A[A-Za-z0-9._%+-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\z/
def initialize(attributes=nil)
attributes.each do |name, value|
send("#{name}=", value)
end unless attributes.nil?
end
def persisted?
false
end
end
There is a valid method on model, which triggers validation.
so instead model.save, try model.valid?