In an app I have 3 types of contact forms - in the model - the attributes :aaa, :bbb, :ccc belongs to the second contact form, the previous attributes belongs to the first contact form.
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :body, :aaa, :bbb, :ccc
validates :name, :email, :body, :aaa, :bbb, :ccc, :presence => true
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
What I am trying to do: I am looking for a way, how to validate attributes for the respective contact forms, specifically:
the first contact form contains attributes: :name, :email, :body, which I need to validate
the second contract form contains attributes: :aaa, :bbb, :ccc, :email, which I need to validate
How to do that? How to distinguish, which attributes belongs to which form and validate them?
If there are 3 types of contract forms why not make them 3 separate classes?
If for some erason you still want to keep it in one class, you can do it using 'with_options' magic:
with_options :if => :is_form_1? do |p|
p.validates_presence_of :attr1
p.validates_presence_of :attr2
p.validates_presence_of :attr3
end
with_options :if => :is_form_2? do |p|
p.validates_presence_of :attr4
p.validates_presence_of :attr5
p.validates_presence_of :attr6
end
def is_form_1?
#some logic
end
def is_form_2?
#some logic
end
Still, I don't like the idea of keeping it in one class.
I'd suggest you think about this in behavioural rather than implementation terms. You mention there are three contact forms, but what is the underlying use that you're putting each one to? You shouldn't be thinking about forms when you're setting up your model.
That having been said, you can achieve what you want using the validation_scopes gem. Using validation_scopes, you can define sets of validation rules that you can treat independently. In your controllers, you can then check whichever set of validation rules apply to the context (i.e. which form the user has filled in).
In your model you can set up validation scopes named for each form (or better, named for the context in a way that has semantic value, but I don't know enough about your app to know what the contexts are), like this:
validation_scope :form_one_errors do |vs|
validates :name, :body, :presence => true
end
validation_scope :form_two_errors do |vs|
validates :aaa, :bbb, :ccc, :presence => true
end
Since email needs to be validated in both contexts, you can just set it up as a normal validation (as per your code in the question).
Then in the controller for, say, form one, you can check the scope to see if there are any errors for that context. Note that you have to check the errors for the validation scope separately for the regular validation errors.
if !message.valid?
# Do something with message.errors
elsif message.has_form_one_errors?
# Do something with message.form_one_errors
else
# All good
end
Related
I have a Question class:
class Question < ActiveRecord::Base
attr_accessible :user_id, :created_on
validates_uniqueness_of :created_on, :scope => :user_id
end
A given user can only create a single question per day, so I want to force uniqueness in the database via a unique index and the Question class via validates_uniqueness_of.
The trouble I'm running into is that I only want that constraint for non-admin users. So admins can create as many questions per day as they want. Any ideas for how to achieve that elegantly?
You can make a validation conditional by passing either a simple string of Ruby to be executed, a Proc, or a method name as a symbol as a value to either :if or :unless in the options for your validation. Here are some examples:
Prior to Rails version 5.2 you could pass a string:
# using a string:
validates :name, uniqueness: true, if: 'name.present?'
From 5.2 onwards, strings are no longer supported, leaving you the following options:
# using a Proc:
validates :email, presence: true, if: Proc.new { |user| user.approved? }
# using a Lambda (a type of proc ... and a good replacement for deprecated strings):
validates :email, presence: true, if: -> { name.present? }
# using a symbol to call a method:
validates :address, presence: true, if: :some_complex_condition
def some_complex_condition
true # do your checking and return true or false
end
In your case, you could do something like this:
class Question < ActiveRecord::Base
attr_accessible :user_id, :created_on
validates_uniqueness_of :created_on, :scope => :user_id, unless: Proc.new { |question| question.user.is_admin? }
end
Have a look at the conditional validation section on the rails guides for more details: http://edgeguides.rubyonrails.org/active_record_validations.html#conditional-validation
The only way I know of to guarantee uniqueness is through the database (e.g. a unique index). All Rails-only based approaches involve race conditions. Given your constraints, I would think the easiest thing would be to establish a separate, uniquely indexed column containing a combination of the day and user id which you'd leave null for admins.
As for validates_uniqueness_of, you can restrict validation to non-admins through use of an if or unless option, as discussed in http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of
Just add a condition to the validates_uniqueness_of call.
validates_uniqueness_of :created_on, scope: :user_id, unless: :has_posted?
def has_posted
exists.where(user_id: user_id).where("created_at >= ?", Time.zone.now.beginning_of_day)
end
But even better, just create a custom validation:
validate :has_not_posted
def has_not_posted
posted = exists.where(user: user).where("DATE(created_at) = DATE(?)", Time.now)
errors.add(:base, "Error message") if posted
end
I have many select options in my forms with a collection of possible options.
E.g.:
title_options = %w[Mr Mrs Miss Ms Dr]
In my view, I will render the select (using formtastic):
<%= f.input :title, :as => :select, :collection => title_options %>
Currently, though, I store the title options in a helper file with many methods for each select:
module SelectHelper
def days_options
...
end
def title_options
...
end
..
end
Then, in a model for validation I can either extend this helper:
class user < ActiveRecord::Base
extend SelectHelper
validates :title, :inclusion => {:in => title_options}
end
or duplicate the options:
class user < ActiveRecord::Base
validates :title, :inclusion => {:in = %w[Mr Mrs Miss Ms Dr]}
end
Is there a better way to store the collection, for example, as a method in the model directly? I don't see it necessary to store these options in a database as they shouldn't ever change.
Well, to simplify things, you can store this collections in a constant inside your class.
class User < ActiveRecord::Base
TITLES = %w[Mr Mrs Miss Ms Dr]
validates :title, :inclusion => {:in => TITLES}
You can try to use enumerated_attribute gem or some of same functionality
What I am basically trying to do is to create a custom validation which calls a RoR default validation with specific options to try and reduce boilerplate (and have this validation to be used globally by all models)
The way to do custom validations on a certain field in RoR is by using the validates_each method, like so
class SelectBooleanValidator < ActiveModel::EachValidator
def validate_each(record,attr,value)
#do validation here
end
end
What I am trying to do is to call the inclusion validator method within the validator_each, so that the select_boolean validation (which I am implementing) just uses the :inclusion validator with certain options, i.e. I want to do something like this (note that this code doesn't actually work, but the below is what I am basically trying to do)
class SelectBooleanValidator < ActiveModel::EachValidator
include ActiveModel::Validations
def validate_each(record,attr,value)
validates_with InclusionValidator, record,attr,value, {:in => [true, false],:message=>'can\'t be blank'}
end
end
And then I would (inside my model) just do this
validates :awesome_field, select_boolean:true
Instead of having to do this all the time
validates :awesome_field, :inclusion => {:in => [true, false], message: 'can\'t be blank'}
class SelectBooleanValidator < ActiveModel::Validations::InclusionValidator
def options
super.merge(:in => [true, false], message: 'can\'t be blank')
end
end
I am new to Rails and Ruby. On my view, I have 2 radio buttons that ask if the person is a resident of the US. If they are, a state select is shown. If they aren't, a country select is shown.
I am trying to validate that a state was selected, if the person is a resident of the US.
How can I create a validation and access the state out of the addresses_attributes?
Here is my model:
class Person < ActiveRecord::Base
has_many :addresses, :as => :addressable
has_one :user
accepts_nested_attributes_for :user, :allow_destroy => true
accepts_nested_attributes_for :addresses
attr_accessor :resident
attr_accessible :campaign_id,
:first_name,
:last_name,
:user_attributes,
:addresses_attributes,
:resident
validates :first_name, :presence => true
validates :last_name, :presence => true
validates_presence_of :resident, :message => "must be selected"
end
These are the relevant parameters being sent:
"resident"=>"true",
"addresses_attributes"=>{"0"=>{"country_code"=>"",
"state"=>""}}
You need custom validation method.
validate :check_state_presence
def check_state_presence
if self.resident && !self.addresses.state.present?
self.errors[:base] << "You need to Select State if you are a US resident."
end
end
You can sort it out using validates_inclusion_of instead.
Ruby API says:
If you want to validate the presence of a boolean field (where the real values are true and >false), you will want to use validates_inclusion_of :field_name, :in => [true, false].
This is due to the way Object#blank? handles boolean values: false.blank? # => true.
+1 to #VelLes for the help in pointing me in the right direction. I am answering my own question because I had to change #VelLes example a bit to get it to work and I want other people to see the full solution.
Since I am using attr_accessor as a virtual attribute, when the true/false value comes in from the radio button, it gets stored as a string. Therefore if self.resident = "false", it will get evaluated to true.
You can do self.resident == 'false' or convert to a boolean and add a new self.resident? method. I chose the latter.
The boolean conversion came from this blog post, add to a file in config/initializers
class String
def to_bool
return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
end
end
My final code is:
validate :check_state_presence
def resident?
resident.to_bool
end
def check_state_presence
if self.resident? && !self.addresses[0].state.present?
#the inline version of the error message
self.addresses[0].errors.add(:state, "must be selected")
end
end
Please let me know if there is a better 'rails' way to do this!
I am encountering a problem which I have never encountered before. I am working on code that was written by another programmer and it is kind of a mess.
Here is the problem. I have the following validations in the my model :
validates_presence_of :subscription_level,
:message => 'please make a selection'
validates_presence_of :shipping_first_name
validates_presence_of :shipping_last_name
validates_presence_of :shipping_address
validates_presence_of :shipping_city
validates_presence_of :shipping_state
validates_presence_of :shipping_postal_code
validates_presence_of :shipping_country
validates_presence_of :billing_first_name
validates_presence_of :billing_last_name
validates_presence_of :billing_address
validates_presence_of :billing_city
validates_presence_of :billing_state
validates_presence_of :billing_postal_code
validates_presence_of :billing_country
validates_presence_of :card_number
validates_numericality_of :card_number
validates_presence_of :card_expiration_month
validates_numericality_of :card_expiration_month
validates_presence_of :card_expiration_year
validates_numericality_of :card_expiration_year
validates_presence_of :card_cvv
validates_numericality_of :card_cvv
I have two actions for the controller in question. One is new and the other is redeem.
I want to perform all of these validations with the new action but want to skip most of them for redeem action.
The problem I am facing right now is that using valid? in the controller is also validating things which are not required for redeem action.
How can I get around this?
It's hacky, but I've had to resort to having an attribute flag that can enable/disable validations in certain states. (My specific example was a multi-page form, where we eventually want to validate all required fields for an object, but we only can validate data that has been submitted on previous pages)
Here's an example of how that might look:
class Whatever < ActiveRecord::Base
attr_accessor :enable_strict_validation
validates_presence_of :name # this always happens
validates_uniqueness_of :name, :if => :enable_strict_validation
end
Then somewhere else (eg your controller), you can do:
#whatever = Whatever.new(...)
#whatever.save # <= will only run the first validation
#whatever.enable_strict_validation = true
#whatever.save # <= will run both validations
Validations are not handled by the controller and as such are not action specific.
Validations can, however, be limited based on the type of update being made to the model. Update, Create, or Save.
Would it work for you to limit the validations only to new records?
validates_numericality_of :card_cvv, :on => :create
If not, you can write custom validators to handle returning true on conditions you specify (such as the controller action), but again it isn't really the "Rails Way".
The simplest example would be using the validate method
validate do
return true if my_action_is_redeem
self.card_cvv =~ /^\d*$/
end
For more info on validations see the docs
you can restrict the validate on the create (new) or on the update (redem operation is a update?).
your validation could be like this:
validates_presence_of :attribute1, :on => :update #so, only on redem method is validated)
You can choose when to validate, on this events: :create, :update, :save (both create or update)
More info on:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#on