Validate only when - ruby-on-rails

I need to validate a value's presence, but only AFTER the value is populated. When a User is created, it is not required to set a shortcut_url. However, once the user decides to pick a shorcut_url, they cannot remove it, it must be unique, it must exist.
If I use validates_presence_of, since the shortcut_url is not defined, the User isn't created. If I use :allowblank => true, Users can then have "" as a shortcut_url, which doesn't follow the logic of the site.
Any help would be greatly appreciated.

Here we are always making sure the shortcut_url is unique, but we only make sure it is present if the attribute shortcut_selected is set (or if it was set and now was changed)
class Account
validates_uniqueness_of :shortcut_url
with_options :if => lambda { |o| !o.new_record? or o.shortcut_changed? } do |on_required|
on_required.validates_presence_of :shortcut_url
end
end
You'll need to test to make sure this works well with new records.

Try the :allow_nil option instead of :allow_blank. That'll prevent empty strings from validating.
Edit: Is an empty string being assigned to the shortcut_url when the user is being created, then? Maybe try:
class User < ActiveRecord::Base
validates_presence_of :shortcut_url, :allow_nil => true
def shortcut_url=(value)
super(value.presence)
end
end

try conditional validations, something like:
validates_presence_of :shortcut_url, :if => :shortcut_url_already_exists?
validates_uniqueness_of :shortcut_url, :if => :shortcut_url_already_exists?
def shortcut_url_already_exists?
#shortcut_url_already_exists ||= User.find(self.id).shortcut_url.present?
end

Related

How can I implement dynamic validation in activerecord

What is the best way to adjust your validation of a model based on a parameter or action? Say I am entering a lead into a system, so all that is required is basic contact info. But then later I may enter a new contract for that user at which point I need more detailed information. So I may need phone number when just entering a lead but once they are entering into a contract I might need their birthdate and alternate phone number.
I considered using a state machine but the user could actually enter into two contracts at the same time so state doesn't really work for this scenario.
I also considered storing the extra info with the contract but since the user can have more than one contract, it needs to live with the user so it is not redundant.
So basically when saving a contract, it would tell me that the attached user is invalid if said user doesn't have the extra fields.
Check out conditional validations:
class Person
validates_presence_of :given_name, family_name
validates_presence_of :phone_number, :email_address, :if => :registered
with_options :if => :registered do |person|
# validations in this block are scoped to a registered user
person.validates_presence_of :dob
end
end
The :if option can take:
a symbol that corresponds to a method on the class that evaluates to true or false
a proc or lambda that returns a value that evaluates to true or false
a string containing ruby code (god knows why you'd want to do that)
You also have access to an :unless option which works in a similar fashion.
You can also encapsulate the logic to determine the current state of the user and use that to determine what validation steps you can take:
class Person
validates_presence_of :email_address, :if => ->(p) { p.state == :pending_confirmation }
# I actually prefer validations in this format
validate do # stricter validations when user is confirming registration
if confirming_membership && (given_name.blank? || family_name.blank?
errors.add(:base, 'A full name is required')
end
end
def state
# your logic could be anything, this is just an example
if self.confirmed_email
:registered
elsif confirming_membership
:pending_confirmation
else
:new_user
end
end
def confirming_membership
# some logic
end
end
You can use conditional validation for example:
validates_presence_of :phone, :if => Proc.new { |p| p.lead? }
In whatever action the lead posts to, you could just do this:
#object.save(validate: false)
Then, when they need to enter the contract, leave off that validate: false option to ensure that those validations run.
Also, see this post if you want to skip only certain validations.

Is it possible to create a callback for a changed mongoid embedded document field in Ruby on Rails?

Is there a way to run a callback only if an embedded document field was changed?
Currently, the following runs the callback on a normal field only if it was changed:
class user
field :email, type: String
embeds_many :connections, cascade_callbacks: true
before_save :run_callback, :if => :email_changed?
before_save :run_connection_callback, :if => :connections_changed? # DOES NOT WORK
end
For anybody seeing this answer in 2015
In Mongoid 4.x model.changed? and model.changes exist and behave like their ActiveRecord counterparts.
Mongoid won't define the method connections_changed? for you, but you can define it yourself by using a virtual field in User to keep track of when an embedded connection gets changed. That is:
class User
# define reader/writer methods for #connections_changed
attr_accessor :connections_changed
def connections_changed?
self.connections_changed
end
# the connections are no longer considered changed after the persistence action
after_save { self.connections_changed = false }
before_save :run_connection_callback, :if => :connections_changed?
end
class Connection
embedded_in :user
before_save :tell_user_about_change, :if => :changed?
def tell_user_about_change
user.connections_changed = true
end
end
One shortcoming of this method is that user.connections_changed only gets set when the document is saved. The callbacks are cascaded in such a way that the Connection before_save callback gets called first and then the User before save callback, which allows the above code to work for this use case. But if you need to know whether any connections have changed before calling save, you'll need to find another method.

Validating a existence of a beta code before creating a User

model: User
has_one :beta_invite
before_save :beta_code_must_exist
def beta_code_must_exist
if beta_invite_id == beta_invite.find_by_name(beta_invite.id)
user
else
nil
end
end
model: BetaInvite
has_many :users
What I`m trying to do is check for the existence of a beta invite in DB, before allowing the user to be saved.
Since the User will be passing in the BetaInvite name into the field, I would like to check if it matches any existing Codes in the DB.
Hope I didn`t mix things up too much.
Would appreciate any help with this problem.
Add a text field to the form for :beta_code
Add an attr_accessor for that field: attr_accessor :beta_code
Then add the following line to the model (Assumes you only want to do this check on user creation):
validate :beta_code_must_exist, :on => :create
Change beta_code_must_exist to add an error to the form. Also be sure to properly cast :beta_code into the correct type.
Warning untested code below
def beta_code_must_exist
#invite = BetaInvite.find_by_name(beta_code)
if #invite.empty?
errors.add(:beta_code, "is not a valid invite code")
else
beta_invite_id = #invite.id
end
end
Use :inclusion with the :in option. You can supply :in with any enumerable:
validates :beta_invite, :inclusion => { :in => BetaInvite.all,
:message => "%{value} is not a valid beta invite code" }
Source: Rails Active Record Validation

validate and update single attribute rails

I have the following in my user model
attr_accessible :avatar, :email
validates_presence_of :email
has_attached_file :avatar # paperclip
validates_attachment_size :avatar,
:less_than => 1.megabyte,
:message => 'Image cannot be larger than 1MB in size',
:if => Proc.new { |imports| !imports.avatar_file_name.blank? }
in one of my controllers, I ONLY want to update and validate the avatar field without updating and validating email.
How can I do this?
for example (this won't work)
if #user.update_attributes(params[:user])
# do something...
end
I also tried with update_attribute('avatar', params[:user][:avatar]), but that would skip the validations for avatar field as well.
You could validate the attribute by hand and use update_attribute, that skips validation. If you add this to your User:
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
if mock.valid?
true
else
!mock.errors.has_key?(attr)
end
end
And then update the attribute thusly:
if(!User.valid_attribute?('avatar', params[:user][:avatar])
# Complain or whatever.
end
#user.update_attribute('avatar', params[:user][:avatar])
You should get your single attribute updated while only (manually) validating that attribute.
If you look at how Milan Novota's valid_attribute? works, you'll see that it performs the validations and then checks to see if the specific attr had issues; it doesn't matter if any of the other validations failed as valid_attribute? only looks at the validation failures for the attribute that you're interested in.
If you're going to be doing a lot of this stuff then you could add a method to User:
def update_just_this_one(attr, value)
raise "Bad #{attr}" if(!User.valid_attribute?(attr, value))
self.update_attribute(attr, value)
end
and use that to update your single attribute.
A condition?
validates_presence_of :email, :if => :email_changed?
Have you tried putting a condition on the validates_presence_of :email ?
http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000083
Configuration options:
if - Specifies a method, proc or string to call to determine if the validation should occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The method, proc or string should return or evaluate to a true or false value.
unless - Specifies a method, proc or string to call to determine if the validation should not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The method, proc or string should return or evaluate to a true or false value.
I am assuming you need this, because you have a multi-step wizard, where you first upload the avatar and the e-mail is filled in later.
To my knowledge, with your validations as they are, I see no good working solution. Either you validate all, or you update the avatar without validations. If it would be a simple attribute, you could check if the new value passes the validation seperately, and then update the model without validations (e.g. using update_attribute).
I can suggest two possible alternative approaches:
either you make sure that the e-mail is always entered first, which I believe is not a bad solution. And then, with each save, the validation is met.
otherwise, change the validation. Why would you declare a validation on a model, if there are records in the database that do not meet the validation? That is very counter-intuitive.
So I would propose something like this:
validate :presence_of_email_after_upload_avatar
def presence_of_email_after_upload_avatar
# write some test, when the email should be present
if avatar.present?
errors.add(:email, "Email is required") unless email.present?
end
end
Hope this helps.
Here is my solution.
It keeps the same behaviour than .valid? method, witch returns true or false, and add errors on the model on witch it was called.
class MyModel < ActiveRecord::Base
def valid_attributes?(attributes)
mock = self.class.new(self.attributes)
mock.valid?
mock.errors.to_hash.select { |attribute| attributes.include? attribute }.each do |error_key, error_messages|
error_messages.each do |error_message|
self.errors.add(error_key, error_message)
end
end
self.errors.to_hash.empty?
end
end
> my_model.valid_attributes? [:first_name, :email] # => returns true if first_name and email is valid, returns false if at least one is not valid
> my_modal.errors.messages # => now contain errors of the previous validation
{'first_name' => ["can't be blank"]}

Dynamic finder methods for validation purposes

I am using Ruby on Rails 3.0.7 and I would like to find some records at run time for validation purposes but passing\setting a value for that finder method. That is, in a my class I have the following:
class Group < < ActiveRecord::Base
validates :relation_id,
:presence => true,
:inclusion => {
:in => ... # Read below for more information about
}
end
If I set :in to be
:in => User.find(1).group_ids
it works, but I would like to set "some-dynamic-things" for the finder method instead of the 1 value stated below in the example. That is, I would like to do something like the following in order to pass to the model a <test_value> in someway:
class Group < < ActiveRecord::Base
validates :relation_id,
:presence => true,
:inclusion => {
:in => User.find(<test_value>).group_ids
}
end
Is it possible? If so, how can I pass the value to the constant?
P.S.: Just to know, I am trying to make that in order to move some logic from the controller to the model.
I'm inferring that what you're trying to do is enforce something like "Only users who are members of a group can save it." If that's the case, you have behavior that should stay in the controller.
Your model doesn't have access to the current session, and adding this logic will prevent you from using your model for other things in the future. For example, you'd never be able to save a group from a batch or maintenance job that wasn't associated with a user.
If you really want to do this you could put a current_user class level variable in the User object and set it in a before_filter...
class ApplicationController
before_fitler :set_current_user
def set_current_user
User.current_user = #however you get your user in your controllers
end
end
class User
##current_user
end
class Group
validates :user_in_group
def user_in_group
return true unless User.current_user #if we don't have a user set, skip validation
User.current_user.group_ids.include? self.id
end
end
It looks like you want something like a proc to be run for the validator for the :in attribute. I think you may be threading in dangerous territory when you rely on load order of models and playing with "dynamic constants".
Instead how about just building your own custom validator for this case?
It's not that hard, and you will have full control of what you need:
http://guides.rubyonrails.org/active_record_validations_callbacks.html#creating-custom-validation-methods

Resources