I am new to rails validation.
I have two fields:
field :feed_entitlements, :type => Array
...
field :alert_news, :type => Boolean, :default => false
I want to put a validation on "alert_news" that requires a count > 0 in "feed_entitlements". Is this possible using a rails validation? It seems like all examples of validations that I can find are simple "can't be blank" type problems.
Thanks.
Add to your model this code
def validate
errors.add_to_base "count should be more then 0" if feed_entitlements.count < 0
end
Related
I am trying to validate the entry a user makes in an amount field.
The field is amount_money
This field is a string which is validated on form submission
monetize :amount, :as => :amount_money
validates :amount, numericality: {only_integer: true}
validates :amount_money, numericality: {greater_than_or_equal_to: 0}
validate :amount_money_within_limit
validate :is_a_valid_number
I want to ensure there are no letters or symbols and that the amount is in an acceptable range.
the code to do this is
def amount_money_within_limit
if amount_money && amount_money.cents > 10_000_00
errors.add(:amount_money, 'cannot exceed $10,000.')
end
if amount_money && amount_money.cents < 1_00
errors.add(:amount_money, 'Problem with Amount')
end
end
this works great for input numbers, of numbers and letters, of letters, of special characters but
If I try Bob - the validation kicks in
but if I try BBob - the validation is bypassed.
If the input contains 2 Capital letters next to each other - it fails.
I've tried a downcase - but that doesn't suit as the field is monetized (money gem) - and the downcase screws up if there is valid input.
If the input to the field contains two uppercase letters - all the validations are bypassed So something like AA is not caught by any on the above validations
Why don't you use regular expressions? Something like this:
def is_a_valid_number? amount_money
amount_money =~ /\d+/
end
It seems you have put 1 validation on the wrong field, you should've put validations only on amount field (your real db field), and not on the amount_money which is automagical field from rails-money gem. I'll apply their documentation on numerical validations to your case:
monetize :amount,
:numericality => {
:only_integer => true,
:greater_than_or_equal_to => 1_00,
:less_than_or_equal_to => 10_000_00
}
You won't need any other custom validations with this setup.
I am using the Rails 4.1 enum field
class User
enum category: [ :client, :seller, :provider ]
end
When the user signs up, he chooses from a select box his category. The default is empty, because I want to force the user to choose one option.
If the user does not select any option, I would like to return to the form with a validation message. Here is the select box code in sign up form
<%= f.select :category, [], {}, class: "form-control" do %>
<option value="99">Choose an option</option>
<% User.categories.each do |cat,code| %>
<option value="<%= code %>" <% if params["user"] && code.to_s == params["user"]["category"] %>selected='selected'<%end%> ><%= t(cat) %></option>
<% end %>
<% end %>
When the controller creates the user, instead of adding a validation error to the record, it raises an exception. How to avoid this?
ArgumentError - '99' is not a valid category:
(gem) activerecord-4.1.1/lib/active_record/enum.rb:103:in `block (3 levels) in enum'
(gem) activerecord-4.1.1/lib/active_record/attribute_assignment.rb:45:in `_assign_attribute'
(gem) activerecord-4.1.1/lib/active_record/attribute_assignment.rb:32:in `block in assign_attributes
I really dislike the reasoning as stated in the issue listed above. Since the value is coming over the wire, it should be treated the same as a freetext input where the expectation is to validate in the model and not the controller. This is especially true in APIs where the developers have even less of a say as far as expected input coming from form data (for example).
In case anyone wants a hack, here is what I came up with. Passes basic testing, but would love feedback if anyone finds issues with it:
class User
enum category: [ :client, :seller, :provider ]
def category=(val)
super val
rescue
#__bad_cat_val = val
super nil
end
end
This will reset category to nil and we can then validate the field:
class User
...
validates :category, presence: true
end
The problem is that we really want to be validating inclusion, not just presence. To get around that we have to capture the input (e.g. as above in def category=). Then we can output our message using that value:
class User
...
validates :category, inclusion: {
in: categories.keys,
message: ->(record,error) {
val = record.instance_variable_get(:#__bad_cat_val)
return "Category is required" if val.nil?
"#{val.inspect} is not a valid category"
}
}
end
That will give us messages for presence and inclusion. If you need to fine tune it more you would have to use a custom validator (I believe).
Read: https://github.com/rails/rails/issues/13971
Specifically read Senny's comment:
The current focus of AR enums is to map a set of states (labels) to an integer for performance reasons. Currently assigning a wrong state is considered an application level error and not a user input error. That's why you get an ArgumentError.
That being said you can always set nil or an empty string to the enum attribute without raising an error:
<option value="">Choose an option</option>
and add a simple presence validation like:
validates :category, presence: { message: "is required" }
The enum in Rails always raise a error if you try to set an invalid value. There is no such validation and add an error message to base or adding a validation error to the record. You should create own validation by rescue errors.
=> u = User.last
=> User.genders
=> {"male"=>0, "female"=>1}
=> u.gender = 'boy'
#> ArgumentError: 'boy' is not a valid gender
=> u.gender = 'male'
#> "male"
In case someone still struggles with this. You can add a standard inclusion validation like this (in the User model):
validates :category, inclusion: {
in: categories.keys,
message: "%{value} is not a valid category"
}
I've got a model, Entity.
class Entity
include Mongoid::Document
field :x
field :y
field :z, type => Hash, :default => {} # new field
end
I added a new field to it, a hash. When I try to use it, I get an error. My code is:
e = Entity.first
if e.z["a"] # if there is a key of this in it?
e.z["a"] = e.z["a"] + 1
else
e.z["a"] = 1
end
But, this error with an undefined method get for hash. If I try to create an initializer for it, to set the values in an existing document, it errors with the same error. What am I doing wrong?
Initializer looks like:
e = Entity.first
e.write_attribute(:z, {})
Thanks
Sorted it.
It seems the answer is to set in Mongoid 1.9.5 the hash to:
field :hash_field, :type => Hash, :default => Hash.new
and it can access and initialize it. Not quite understanding why, but happy to have the answer !
With this model:
validates_presence_of :email, :message => "We need your email address"
as a rather contrived example. The error comes out as:
Email We need your email address
How can I provide the format myself?
I looked at the source code of ActiveModel::Errors#full_messages and it does this:
def full_messages
full_messages = []
each do |attribute, messages|
messages = Array.wrap(messages)
next if messages.empty?
if attribute == :base
messages.each {|m| full_messages << m }
else
attr_name = attribute.to_s.gsub('.', '_').humanize
attr_name = #base.class.human_attribute_name(attribute, :default => attr_name)
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
messages.each do |m|
full_messages << I18n.t(:"errors.format", options.merge(:message => m))
end
end
end
full_messages
end
Notice the :default format string in the options? So I tried:
validates_presence_of :email, :message => "We need your email address", :default => "something"
But then the error message actually appears as:
Email something
So then I tried including the interpolation string %{message}, thus overriding the %{attribute} %{message} version Rails uses by default. This causes an Exception:
I18n::MissingInterpolationArgument in SubscriptionsController#create
missing interpolation argument in "%{message}" ({:model=>"Subscription", :attribute=>"Email", :value=>""} given
Yet if I use the interpolation string %{attribute}, it doesn't error, it just spits out the humanized attribute name twice.
Anyone got any experience with this? I could always have the attribute name first, but quite often we need some other string (marketing guys always make things more complicated!).
Errors on :base are not specific to any attribute, so the humanized attribute name is not appended to the message. This allows us to add error messages about email, but not attach them to the email attribute, and get the intended result:
class User < ActiveRecord::Base
validate :email_with_custom_message
...
private
def email_with_custom_message
errors.add(:base, "We need your email address") if
email.blank?
end
end
Using internationalization for this is probably your best bet. Take a look at
http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
Particularly this section:
5.1.2 Error Message Interpolation
The translated model name, translated
attribute name, and value are always
available for interpolation.
So, for example, instead of the
default error message "can not be
blank" you could use the attribute
name like this : "Please fill in your
%{attribute}"
In my form I would like users to type a date in DD/MM/YYYY format. I have validation that checks that.
The problem is that in the database it is stored in YYYY-MM-DD format, so if try just to update the is_money_paid field:
job = Job.find(params[:id])
job.update_attributes(:is_money_paid => ...)
the validation fails saying that job's date is in wrong format (YYYY-MM-DD rather than DD/MM/YYYY).
What would be an appropriate way to solve this ?
Here is the relevant code:
class Job < ActiveRecord::Base
validate :job_date_validator
end
DATE_REGEX = /\A\d{2}\/\d{2}\/\d{4}\z/
def job_date_validator
if job_date_before_type_cast.blank?
errors.add(:job_date, "^Date must be selected")
elsif job_date_before_type_cast !~ DATE_REGEX
errors.add(:job_date, "^Date must be in DD/MM/YYYY format")
end
end
is_money_paid is a boolean field.
I would change the validator to say:
validate_presence_of :job_date, :message => "must be selected"
validate :job_date_validator, :if => :job_date_changed?
Or something along those lines.
You can take out the .blank? check too.
Format it in before_validation
http://guides.rubyonrails.org/active_record_validations_callbacks.html#updating-an-object
I would suggest you to see gem validates_timeliness which is rails 3 compatible.