Applying validation exception when using state_machine - ruby-on-rails

I have been unable to find a precise way to accomplish a validation exception on my rails model when using state_machine.
I have as expected a state column in my model and process a validation of emails by passing a user from unverified to verified, simple enough.
My model contains a normal validation of passwords
validates :password, length: {minimum: 6}
validates :password_confirmation, presence: true
This validation is important, but when passing my user from an unverified to a verified state I run into problems with my model validation as I am only applying an update to a single column, but active record integration with state_machine seems to apply a record update.
The state_machine snippet:
state_machine :initial => :unverified do
event :verify do
transition :unverified => :verified
end
So the solution would seem to apply an exception to the model validation, I was drawn to :unless as a solution..
I implemented something that I have to say I did not understand fully, which seems to now have the effect of removing any validation on password, obviously making my state_machine process work, but not meeting my objective..
my changes where to add:
validates :password, length: {minimum: 6}, :unless => :proc_no_password_validation
validates :password_confirmation, presence: true, :unless => :proc_no_password_validation
and apply in a private method:
def proc_no_password_validation
Proc.new { |user| user.verify }
end
I have a feeling that directionally I am on the right track, but help to spot my mistake or issue or other solution would be much appreciated.
UPDATE
So I have had some help from Jef on this, but my conclusion was that the model validation method exception route was wrong, I modified my state code as follows:
state_machine :state, :initial => :unverified, :action => :bypass_validation do
event :verify do
transition :unverified => :verified
end
event :unverify do
transition :verified => :unverified
end
end
and then a validation bypass on the state I am interested in ignoring
def bypass_validation
if self.changes['state'][1] == 'verified'
save!(:validate => false)
else
save!(:validate => true)
end
end

:unless accepts a method name as a symbol or a proc. You gave the name of a method that returns a Proc. As a Proc is not false, your unless condition is met and your validation skipped.
You should use one of :
validates :password_confirmation, presence: true, :unless => :proc_no_password_validation
def proc_no_password_validation
self.verify
end
validates :password_confirmation, presence: true, :unless => Proc.new{|user| user.verify}
UPDATE
state_machine allows you to wrap your validations into states :
state :unverified { # or :verified, when should validation apply ?
validates :password ...
validates :password_confirmation ...
}

Related

Skip all validations on state machine method

I am trying to figure out how to skip validations on a specific instance of an ActiveRecord object when my reservation model transitions on state machine via a rake task. I would like to skip all validations whenever reservation.close! is called. Would hope to call something like reservation.close!(:validate => false). FYI we are using https://github.com/pluginaweek/state_machine for state machine.
Here is a sample of my reservation model.
class Reservation < ActiveRecord::Base
validates :ride_id, :start_at, :end_at, presence: true
validate :proper_custom_price?, allow_nil: true, on: :update
validate :dates_valid?
validate :dates_make_sense?
scope :needs_close_transition, lambda { where("end_at < ?", Time.now).where("state" => ["requested", "negotiating", "approved"]) }
state_machine :initial => 'requested' do
all_prebooked = ["requested", "negotiating", "approved"]
event :close do
transition :from => all_prebooked,
:to => "precanceled"
end
before_transition :on => [:close] do |reservation|
reservation.cancel_reason = :admin
end
end
end
Here is a sample of my rake task.
namespace :reservation do
task :close => :environment do
puts "== close"
Reservation.needs_close_transition.each do |reservation|
puts "===> #{reservation.id}"
begin
reservation.close!(:validate => false)
rescue Exception => e
Airbrake.notify(e, error_message: e.message) if defined?(Airbrake)
end
end
end
I had the same problem, however I did not want to alter my current validations, so I checked the state machine code (version 1.2.0) and I found another solution, for your specific case it would be something like:
reservation.close!(false)
reservation.save(validate: false)
That will trigger all callbacks that you have defined in your state machine, and well this solution is working well for me.
When using the state_machine gem, the state attribute is updated before the validations are run, so you can add an unless condition to the validation that tests the current state:
validates :start_at, :end_at, presence: true, unless: Proc.new {state == 'closed'}
If you want more complex logic, pass a method name symbox to unless instead of a proc:
validates :start_at, :end_at, presence: true, unless: :requires_validation?
def requires_validation?
# complex logic to determine if the record should be validated
end

Different types of validations in rails

I have a User model
is there a difference between
class User < ActiveRecord::Base
validates :name, :presence => true
end
and
class User < ActiveRecord::Base
def validate
errors.add_to_base "name should not be nil" if name.nil?
end
end
The validates macro is more flexible, as it also allows you to do things like:
validates :name, :format => { :with => /\A[a-zA-Z]+\z/,
:message => "Only letters allowed" }, :length => { :in => 6..20 }
The validate method is really a quick and easy way to do custom validations when existing ones do not exist. (When custom validations get too complex, then you should usually move them into custom validators and use the validates macro).
See more at http://guides.rubyonrails.org/active_record_validations_callbacks.html
Yes -- the first will fail to save an empty string, whereas the second will allow it.

attr_accessor and password validation on update

I have this code in my user model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates :email, :presence => true,
:uniqueness => { :case_sensitive => false },
:format => { :with => /\A[^#]+#[^#]+\z/ },
:length => 7..128
validates :password, :presence => true,
:confirmation => true,
:length => 6..128
private
def encrypt_password
return unless password
self.encrypted_password = BCrypt::Password.create(password)
end
end
Now in my controller when I'm updating some user fields with
#user.update_attributes(params[:user])
the password field is always validated, even when it is not set in the params hash. I figured that this is happening because of the attr_accesor :password which always sets password = "" on update_attributes.
Now I could simply skip the validation of password if it is an empty string:
validates :password, :presence => true,
:confirmation => true,
:length => 6..128,
:if => "password.present?"
But this doesn't work because it allows a user to set an empty password.
Using update_attribute on the field I'd like to change is not a solution because i need validation on that attribute.
If I pass in the exact parameter with
#user.update_attributes(params[:user][:fieldname])
it doesn't solve the problem because it also triggers password validation.
Isn't there a way to prevent attr_accesor :password from always setting password = "" on update?
New answer
This works for me:
validates :password, :presence => true,
:confirmation => true,
:length => { :minimum => 6 },
:if => :password # only validate if password changed!
If I remember correctly it also took me some time to get this right (a lot of trial and error). I never had the time to find out exactly why this works (in contrast to :if => "password.present?").
Old answer - not really useful for your purpose (see comments)
I get around this problem by using a completely different action for password update (user#update_password). Now it is sufficient to only validate the password field
:on => [:create, :update_password]
(and also only make it accessible to those actions).
Here some more details:
in your routes:
resources :users do
member do
GET :edit_password # for the user#edit_password action
PUT :update_password # for the user#update_passwor action
end
end
in your UsersController:
def edit_password
# could be same content as #edit action, e.g.
#user = User.find(params[:id])
end
def update_password
# code to update password (and only password) here
end
In your edit_password view, you now have a form for only updating the password, very similar to your form in the edit view, but with :method => :put and :url => edit_password_user_path(#user)
The solution I have started using to get round this problem is this:
Start using ActiveModel's built in has_secure_password method.
At console
rails g migration add_password_digest_to_users password_digest:string
rake db:migrate
In your model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :login_name, :password, :password_confirmation
# secure_password.rb already checks for presence of :password_digest
# so we can assume that a password is present if that validation passes
# and thus, we don't need to explicitly check for presence of password
validates :password,
:length => { :minimum => 6 }, :if => :password_digest_changed?
# secure_password.rb also checks for confirmation of :password
# but we also have to check for presence of :password_confirmation
validates :password_confirmation,
:presence=>true, :if => :password_digest_changed?
end
And finally,
# In `config/locales/en.yml` make sure that errors on
# the password_digest field refer to "Password" as it's more human friendly
en:
hello: "Hello world"
activerecord:
attributes:
user:
password_digest: "Password"
Oh, one more thing: watch the railscast

validate email in rails after updating

In my user model, I have
validates :email, :presence=>true,
:format => { :with => email_regex },
:uniqueness => true
In my controller, I update the email if a user chooses to change it like this:
#user.update_attribute("email","#{#new_email}")
However, it doesn't throw an error if the format is not honored.
update_attribute does no validations. use
#user.update_attributes({ :email => #new_email })
instead.
I found out that update_attribute skips checking for validation but update_attributes doesn't! Interesting.
http://apidock.com/rails/ActiveRecord/Base/update_attributes

Why aren't my before_validation methods firing if some validation is scoped?

In my model I've got a couple of methods to populate attributes of an Invoice before it is validated:
validates :account_id, :presence => true
validates :account_address, :presence => true
validates :number, :presence => true
validates :number, :uniqueness => true, :scope => :client_id
before_validation :generate_number, :associate_addresses, :on => :create
def generate_number
self.number = self.client.invoices.count + 1
end
def associate_addresses
self.account_address = self.account.addresses.first
end
And in the controller:
#invoice = #account.invoices.build(:client_id => #client.id)
if #invoice.save
#it saved
end
My problem is that the associate_addresses and generate_number methods only fire if I remove the :scope => :client_id argument on the :number validation.
Why would it skip the before_validation callbacks due to this?
Working in Rails 3.0.3
Thanks!
Thanks.
Don't know why it's skipping the before_validation methods, but to scope a uniqueness validation in Rails 3 you should use the following syntax:
validates :number, :presence => true, :uniqueness => { :scope => :client_id }
I guess that your syntax is making it try to add a scope validation, which doesn't exist. Probably there's a Rails bug that makes that skip the before_validation methods.

Resources