How to validate presence of an array using Enumerize? - ruby-on-rails

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).

Related

Datatype validation in rails ActiveModel::Validations

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

Rails: validating a field is present only if another is present

I have a model where there are two fields that can technically be null. The field names are :is_activated and :activated_at. :activated_at is only required if :is_activated is set to true. It does not need to be present if :is_activated is false. What's the appropriate way in Rails to set this validation directly into ActiveRecord?
You can use a Proc on the :activated_at validator.
validates :activated_at, :presence, if: Proc.new { |a| a.is_activated? }
Recommended Reading:
http://guides.rubyonrails.org/active_record_validations.html#using-a-proc-with-if-and-unless
http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
Finally, you should consider renaming :is_activated to simply :activated. This is considered better practice in Rails. The is_ prefix is used in other languages for boolean attributes because their method names don't support a ? character. Ruby does, and Rails generates ___? methods on boolean attributes. Because of this it's better to just call the attribute :activated and check it with activated?.
Non-Boolean Attributes.
If you're not using a Boolean attribute, like is_activated, and want to ensure that one attribute is present when another, non-Boolean attribute is present, you can simplify it:
validates :activated_at, presence: true, if: :activated_by?
activated_by? will return false when null and true otherwise.
You could do something like this:
validates :activated_at, presence: true, if: :is_activated?
def is_activated?
self.is_activated
end
This will only validate :activated_at if the method is_activated? returns true.

rails validate in model that value is inside array

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

Rails validation context

I need help with my ActiveRecord model. I have context based validations (mis)using the build-in context options for validations:
validates :foo, :on => :bar, :presence => true
model = Model.new
model.foo = nil
model.valid? # => true
model.save # works as expected
model.valid?(:bar) # => false
model.save(:context => :bar) # fails and returns false
But using my model in a accepts_nested_attributes_for :model and calling parent.save fails (the validation gets called and returns false), any suggestions or solutions?
Still no answer? To explain more about my problem: I have a model called Form which has many Fields. Users should see validation errors on submit, but the form should be saved anyway (with and without errors). There are different types of Fields, each with global validations (to ensure database consistency) and its own specific user-defined validations (to validate user-entered data). So my Fields look someway like that:
# Global validations, to ensure database consistency
# If this validations fail, the record should not be saved!
validates_associated :form, :on => :global
...
# Specific user-defined validations
# If this validations fail, the record should be saved but marked as invalid. (Which is done by a before_save filter btw.)
def validate
validations.each do |validation| # Array of `ActiveModel::Validations`, defined by the user and stored in a hash in the database
validation.new(:on => :specific).validate(self)
end
end
In my controller:
# def create
# ...
form.attributes = params[:form]
form.save!(:global)
form.save(:specific)
Is something similar possible in Rails using the built-in functionality? Btw this not my actual code, which is quite complicated. But I hope, you guys will get the idea.
Try conditional validations
class Customer
attr_accessor :managing
validates_presence_of :first_name
validates_presence_of :last_name
with_options :unless => :managing do |o|
o.validates_inclusion_of :city, :in=> ["San Diego","Rochester"]
o.validates_length_of :biography, :minimum => 100
end
end
#customer.managing = true
#customer.attributes = params[:customer]
#customer.save
"Ability to specify multiple contexts when defining a validation" was introduced in Rails 4.1 - check validate method, :on options description
Only for Rails 5+:
You are looking for
with_options on: :custom_event do
validates :foo, presence: true
validates :baz, inclusion: { in: ['b', 'c'] }
end
To validate or save use
model = YourModel.new
# Either
model.valid?(:custom_event)
# Or
model.save(context: :custom_event)
Change has_nested_attributes_for :model to accepts_nested_attributes_for :models.
Hope this helps.
Good luck.

preventing rails validation based on previous validation

I have a model with 2 validations on the 'name' attribute. It goes something like this:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") if name_is_dumb?
end
I don't want the 2nd validation to run if the first validation fails (the name is not unique).
What's the best and cleanest way to do this?
According to the documentation:
Callbacks are generally run in the
order they are defined, with the
exception of callbacks defined as
methods on the model, which are called
last.
So the following snippet should work:
validates :name, :uniqueness => true
validate do
errors.add(:name, "is dumb") unless errors[:name].nil?
end

Resources