Dynamic form validation in Rails - ruby-on-rails

I have a form which I want to validate. The validation is based on properties in a couple of other model objects, but the form itself does not correspond to a ActiveRecord model.
Would it be possible to use ActiveModel to achieve this?
class Person < ActiveModel
has_one :shoe
validates :name, :length => { :maximum => self.shoe.size }
end
I basically want to validate a form based on the properties of another model object. Is this possible in anyway?

class Person
include ActiveModel::Validations
# has_one :shoe # This won't work
validates :validates_name_length
private
def validates_name_length
errors.add :name, 'too long' if name && name.length > shoe.size
end
end

Related

Virtual attributes Rails - Unknown Attribute Error

I'm making a Rails 4 application. Each incident belongs to a user. Each incident has many events. I want that I can create an Incident with current_user.incidents.new and pass it an attribute message which exists in the Event model. On creation, I wish to create a new event with the said message.
Here's my Incident model.
class Incident < ActiveRecord::Base
# The 'incident' model.
# Incidents are created by Users (belongs_to :user)
# Incidents may be public or private.
# Incidents have many events (identified, fixing, etc.)
belongs_to :user
has_many :events
validates :name, presence: true, length: {maximum: 256}
validates_presence_of :component
validates_presence_of :public
validates_presence_of :user_id
attr_accessor :message
validates_associated :message, presence: true
def message
Event.find_by_incident_id(self.id).message
end
def message=(value)
self.events.new(message: value, status: self.status)
end
private
def incident_params
params.require(:incident).permit(:name, :status, :user_id, :message)
end
end
But, when I run #i = Incident.new(message: 'something'), I get ActiveRecord::UnknownAttributeError: unknown attribute 'message' for Incident.
Please help me figure this out.
The problem is that you're passing the values to the default ActiveRecord::new method for Incident, which won't care about your getters, setters and accessors, it goes straight to the columns.
Some people override or adapt the build method that can implement clever logic like you require, without affecting the base initialize method.
Because your Incident model inherits from ActiveRecord::Base, it inherits the constructor. The ActiveRecord constructor only accepts model attributes (db columns) as params. You are trying to call an arbitrary instance method. You could create a class method that creates an instance then adds the message:
def self.create_with_message(msg)
i = Incident.new
i.message(msg)
return(i)
end

Raise validation error from virtual attribute in Rails

I got two models: Source and SourceType. Source of course belongs to SourceType.
I want to create new source and assign proper sourcetype object to it. 'Proper' means that one virtual attribute of the source object match some of sourceType objects test regexpression, which becomes source's Type.
I got an attribute writer in source object
class Source < ActiveRecord::Base
belongs_to :source_type
def url=(value)
SourceType.each do |type|
# here i match type's regexp to input value and if match,
# assign it to the new source object
end
end
end
I don't want to build any custom validator for it'll be need to run through SourceTypes twice. How to raise validate error if no sourcetypes is fit to the input so user could see error reasons in a form?
Validation
If you set the virtual attribute using attr_accessor, you should be able to validate on the model you're sending data to (alternatively using inverse_of if you'd like to validate on the nested model):
http://api.rubyonrails.org/classes/ActiveModel/Validator.html
This can now be used in combination with the validates method (see ActiveModel::Validations::ClassMethods.validates for more on this).
class Person
include ActiveModel::Validations
attr_accessor :title
validates :title, presence: true
end
Code
I'd do this:
class Source < ActiveRecord::Base
belongs_to :source_type, inverse_of: :sources
attr_accessor :url
end
class SourceType < ActiveRecord::Base
has_many :sources, inverse_of: :source_type
validates :source_type_attr, presence: { if: :url_match? }
def url_match?
self.sources.url == [your_regex]
end
end

How to perform an inclusion validation on a serialized attribute?

I have a model with an serialized attribute (array). I would like to validate the model only if each member of the array is included within the pre-determined options.
Example:
I have a Person model which has a "mood" attribute. Users can have more than one mood, but each mood must be either 'happy', 'sad', 'tired' or 'angry'.
The model would be something like this:
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
# validates :inclusion => { :in => MOODS }
attr_accessible :mood
serialize :mood
end
The commented validation doesn't work. Is there any way to make it work or do I need a custom validation?
(Note: I don't want to create a separate Mood model.)
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
validate :mood_check
attr_accessible :mood
serialize :mood
protected
def mood_check
mood.each do |m|
errors.add(:mood, "#{m} is no a valid mood") unless MOODS.include? m
end
end
end
Leaving this here in case it helps anyone in the future - I've written a gem to better handle validating serialized attributes. You can just put those validations in a block syntax, the ways you might expect to:
class Person < ActiveRecord::Base
MOODS = %w[happy sad tired angry]
attr_accessible :mood
serialize :mood
validates_array_values :mood, inclusion: { in: MOODS }
end
https://github.com/brycesenz/validates_serialized

Retrieve validating field names

I am using Ruby on Rails 3 and I would like to retrieve validating field names. That is, I defined some validation for a class and I would like to retrieve what fields (their names) are candidate for validation on submitting a form.
I need that because I would like to "play" with class error attributes (<name_class>.errors).
How can I do?
You can access your model's validators method. This will return an array of validators on your model.
For example, if you had this:
class User < ActiveRecord::Base
validates :name, :presence => true
validates :email, :uniqueness => true
end
Then you could access the validators like this:
User.validators
# => [#<ActiveModel::Validations::PresenceValidator:0x123456 #attributes=[:name], #options={}>....]
User.validators.first.attributes
# => [:name]
User.validators.first.class
# => ActiveModel::Validations::PresenceValidator

Rails setting OR conditions in validate_presence_of in a model?

In a rails model, is it possible to do something like
class Example < ActiveRecord::Base
#associations
validates_presence_of :item_id, (:user_id OR :user_email)
#functions
end
Where the model has 3 columns of :item_id, :user_id, and :user_email?
I want the model to be valid as long as I have a :user_id or a :user_email.
Idea being that if the item is recommended to a person who isn't currently signed up, it can be associated via email address for when the recommended person signs up.
Or is there a different method that I can use instead?
One approach is to wrap those fields as a virtual attribute, say:
class Example < ActiveRecord::Base
validates_presence_of :referral
def referral
user_id || user_email
end
end
or you can just throw a custom validate validation method. See custom validations on the Rails API
If both user_id and user_email come from another model, perhaps it's better to add the association instead
class Example
belongs_to :user
validates_associated :user
before_validate :build_user_from_id_or_email
def build_user_from_id_or_email
# ... Find something with the parameters
end
end
validates_presence_of :item_id
validates_presence_of :user_id, :if => Proc.new{ |x| x.user_email.blank? }
validates_presence_of :user_email, :if => Proc.new{ |x| x.user_id.blank? }

Resources