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
Related
I have a model class as follows:
class DataInfo < ActiveRecord::Base
STATUS = {:UNAPPROVED => 1, :APPROVED => 2, :PROCESSED => 3 }
attr_accessible :id, :owner, :status
validates :status, :inclusion => {:in => STATUS.values}
end
I have written a observer as follows:
class StatusObserver < ActiveRecord::Observer
def after_save(datainfo)
if datainfo.status.eql? "APPROVED"
DataInfoWorker.perform_async datainfo.id
end
end
end
How do I test the observer using RSpec? Do I necessarily have to use "No Peeping Toms" plugin? I mainly want test after_save part of it?
You should be able to do something like this using rspec-3 syntax:
describe DataInfo do
describe 'after save when status is APPROVED' do
let(:data_info) { DataInfo.new(status: :APPROVED) }
before do
data_info.save!
end
it 'queues DataInfoWorker' do
expect(DataInfoWorker.jobs.size).to eq 1
end
end
end
Updated to test that a Sidekiq job is queued. Make sure that the rspec-sidekiq gem is added to your Gemfile.
Right so I have one user table with different users. I need to validate user tables on update prior to them moving onto the main site.
so far I've just done the following, is there a way I can block the validations depending on a role without doing a custom validation method, like with_options :on => :update
before_validation :check_role
if check_role = "developer" do |dev|
dev.validate :first_name, presence: true # this doesn't work btw...
end
def check_role
return self.role_type unless self.role_type == nil
end
I figure it out and this looks like the best way to do it:
class YourModel
with_options :if => lambda { |o| o.whatever == "whatever" } do |on_condition|
on_condition.validates_presence_of :address
on_condition.validates_presence_of :city
end
with_options :if => lambda { |o| o.condition_the_second == "whatever" } do |on_condition|
on_condition.validates_presence_of :foo
on_condition.validates_presence_of :bar
end
end
validates :first_name, presence: true, if: :developer?
def developer?
role == 'developer'
end
with_options :if => Proc.new {|user| user.role_type == 'developer'} do |developer|
developer.validates :first_name, :presence => true
end
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 ...
}
I have model Article, for example. Article has_one ArticleContent. ArticleContent has validation of all it's attributes by default. But I need additional functionality - to save draft Article, without any validation.
So I pass :draft => false as one of a parameter in Article.new(), next I do #article.build_article_content(). There is some not working code in ArticleContent:
def draft?
raise self.article.draft
end
validates_presence_of :header, :message => "We have no fuckin' header!", :unless => :draft?
Of course it's not work. At the moment of draft? execution there is no any suitable Article object anywhere, so self.article returns nil. Nice try, codemonkey...
Anyone have some sweet ideas? I think to make #content.save! is not a very good idea
UPDATE
I tried so:
def draft
self[:draft]
end
def draft=(value)
self[:draft] = value
end
def draft?
self[:draft]
end
validates_presence_of :field1, :message => "msg1", :unless => :draft?
validates_presence_of :field2, :message => "msg2", :unless => :draft?
validates_presence_of :field3, :message => "msg3", :unless => :draft?
It works, but how can I group this?
unless self.draft?
validates_presence_of :field1, :message => "msg1"
validates_presence_of :field2, :message => "msg2"
validates_presence_of :field3, :message => "msg3"
end
Says that draft? method is not found. Also i should do
#article.content.draft = #article.draft
And it looks like dirty-dirty hack too
This is a common use case for a state machine. There are several rails plugins that provide for those.
http://ruby-toolbox.com/categories/state_machines.html
If you don't need a full state machine implementation it could still be instructive to have a state column in your ArticleContent model. Its values would be "new", "draft", "published" and so on. Your validations would look at that column's value when deciding what to do, like:
validates :content, :presence => true, :unless => Proc.new { |a| a.state == "Draft" }
(I'm pretty sure that's not the correct syntax but you should get what I'm aiming at.)
To answer your UPDATE
Try with_options.
with_options :unless => :draft? do |o|
o.validates_presence_of :field1, :message => "msg1"
o.validates_presence_of :field2, :message => "msg2"
o.validates_presence_of :field3, :message => "msg3"
end
Looking at your code there's a couple of smells. In order to flunk a validation the thing to do is errors.add(blah), not raise an exception. Also, your methods defined for accessing the draft column look a little redundant. They're just doing what AR would do anyway.
I can't see what I'm missing, but something is obviously not right.
In model:
validates :terms, :acceptance => true, :on => :update
Trying a few options:
>> a = Factory(:blog_agreement)
=> #<BlogAgreement id: 54, terms: false, created_at: "2011-01-20 11:33:03", updated_at: "2011-01-20 11:33:03", accept_code: "fa27698206bb15a6fba41857f12841c363c0e291", user_id: 874>
>> a.terms
=> false
>> a.terms = true
=> true
>> a.save
=> false
>> a.terms = "1"
=> "1"
>> a.save
=> false
>> a.terms = 1
=> 1
>> a.save
=> false
>> a.errors.full_messages
=> ["Terms must be accepted"]
Updated answer..
So it turns out that the problem was having terms as an actual column in the table. In general validates_acceptance_of is used without such a column, in which case it defines an attribute accessor and uses that for its validation.
In order for validates_acceptance_of to work when it maps to a real table column it is necessary to pass the :accept option, like:
validates :terms, :acceptance => {:accept => true}
The reason for this has to do with typecasting in Active Record. When the named attribute actually exists, AR performs typecasting based on the database column type. In most cases the acceptance column will be defined as a boolean and so model_object.terms will return true or false.
When there's no such column attr_accessor :terms simply returns the value passed in to the model object from the params hash which will normally be "1" from a checkbox field.
In the case of someone has the same problem like me with devise, i add this answer:
i added to the devise's registration form:
sign_up.html.erb
<%= f.check_box :terms_of_service %>
user.rb
validates, :terms_of_service, acceptance: true
i forgot to add :terms_of_service inside my configured_permitted_parameters and devise ignored the checkbox state.
application_controller.rb
before_filter :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, :terms_of_service)}
end
The configure_permitted_parameters method is used by devise for know what params he should be save in addition of email and password.
I had to use this format:
validates :accpeted_terms, :acceptance => {:accept => true}
validates_acceptance_of :terms, :accept => true
I found Candland's answer above for validates acceptance to be correct in Rails 3.1. This is how I set up my Rails 3.1.3 app to record the acceptance to the database.
In the migration,
class AddTermsToAccount < ActiveRecord::Migration
def change
add_column :accounts, :terms_of_service, :boolean, :default => false
end
end
In the model,
attr_accessible :terms_of_service
validates :terms_of_service, :acceptance => {:accept => true}
In the form,
<%= f.check_box :terms_of_service %>
<%= f.label :terms_of_service %>
I have tried this from Angular JS and Rails 4. Angular send parameter with true value but rails did not recognize true.
It can receive an :accept option, which determines the value that will
be considered acceptance. It defaults to "1" and can be easily
changed.
So, I change into this:
class Person < ActiveRecord::Base
validates :terms_of_service, acceptance: { accept: true }
end
If you have default parameter is 1 or 0. Try to do this:
class Person < ActiveRecord::Base
validates :terms_of_service, acceptance: true
end
This is for more documentation. acceptance validation.
I hope this help you.
A call to a factory creates the record, so your subsequent calls to save are in fact updates, so your validation fails as intended. Try it without the factory and it should work.
if you have a basic checkbox in your view, such as
<%= builder.check_box :agreement %>
just put this line in your model
validates :agreement, :acceptance => true
which uses the default "1" generated by the check_box view helper