Ruby on Rails SystemStackError stack level too deep validate Wicked gem - ruby-on-rails

Calling validate :method_name causes an infinite loop SystemStackError.
Doing the validation directly inline allows the validation to pass without error.
There must be something I'm overlooking or doing wrong...
The validations worked fine when doing them directly in the model instead of within the conditional_reservation_validation.
Example code that causes SystemStackError stack level too deep
The basic process has been built following this example:
Building Partial Objects Step by Step
I know this needs to be refactored / cleaned up.
Snippet from model:
validate :conditional_reservation_validation
def conditional_reservation_validation
if created_by_admin?
validates_presence_of :vehicle_location
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers
else
if active_or_parking?
validates_presence_of :vehicle_location
# SystemStackError occurs when validate :parking_agreement_if_location fires
validate :parking_agreement_if_location
end
if active_or_pickup?
# additional validations
end
end
# ...
end
def parking_agreement_if_location
if vehicle_in_special_parking_location(vehicle_location)
if self.parking_agreement == true
# ok
else
self.errors.add :base, "Must read and understand parking instructions."
end
end
end
def vehicle_in_special_parking_location(vehicle_location)
parking_locations = Location.where(require_parking: true)
if parking_locations.include?(vehicle_location)
return true
else
return false
end
end
# methods to check the step in process
def active_or_parking?
status.include?('parking') || active?
end
Calling validate :parking_agreement_if_location triggers a SystemStackError
Example code that stops the error:
Just taking the code out of the :parking_agreement_if_location method and putting it directly inline stops the SystemStackError.
validate :conditional_reservation_validation
def conditional_reservation_validation
if created_by_admin?
validates_presence_of :vehicle_location
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers
else
if active_or_parking?
validates_presence_of :vehicle_location
if vehicle_location
locations = Location.where(require_parking: true)
if locations.include?(vehicle_location)
if parking_agreement == true
# ok
else
self.errors.add :base, "Must read and understand parking instructions."
end
# validate :parking_agreement_if_location
end
end
end
if active_or_pickup?
# These methods cause SystemStackError as well...
validate :pickup_date_in_range
validate :pickup_date_in_future
end
end
end
Controller update action:
def update
params[:reservation][:status] = step.to_s
params[:reservation][:status] = 'active' if step == steps.last
case step
when :parking
#reservation.assign_attributes(reservation_params)
when :pickup
#reservation.assign_attributes(reservation_params)
when :billing
#reservation.assign_attributes(reservation_params)
end
render_wizard #reservation
end

You are using validations wrong way. They need to be invoked on class level.
You need to use conditional validations instead:
validates_presence_of :vehicle_location, if: :created_by_admin?
validates_presence_of :pickup, :dropoff, :departure_date, :departure_time, :passengers, if: :created_by_admin?
validates_presence_of :vehicle_location, unless: :created_by_admin?, if: :active_or_parking?
validate :parking_agreement_if_location, unless: :created_by_admin?, if: :active_or_parking?

Related

How do I validate password on the base of a condition?

I want the password should be mandatory for web registration only and not for mobile app registration.
My validation code is below:
class User < ApplicationRecord
validate :password_for_web
validate :password_confirmation_for_web
def password_for_web
if !app_user && password.blank?
errors.add(:password, "can't be blank.")
end
end
def password_confirmation_for_web
if !app_user && password != self.password_confirmation=
errors.add(:password_confirmation, "doesn't match.")
end
end
end
Validation is working properly but when in case of mobile app registration it is still requiring password. Help regarding the issue would be appreciable.
You can use the if: and unless: options to toggle validations:
class User < ApplicationRecord
validates :password, presence: true, confirmation: true, unless: :app_user?
# or
validates :password, presence: true, confirmation: true, if: :web_user?
# TODO implement app_user?
end
You can pass a symbol (a method name) or a lambda.
If you are using simple rails app with responsive design.
You need to first check device is mobile or other device. This you can do in many ways.
Custom Way :
In application_helper.rb:
def mobile_device
agent = request.user_agent
return "tablet" if agent =~ /(tablet|ipad)|(android(?!.*mobile))/i
return "mobile" if agent =~ /Mobile/
return "desktop"
end
Then you can use it in your views:
<% if mobile_device == "mobile" %>
//add extra parameter to check in model
<% form_tag :mobile_device, true %>
<% end %>
In your model :
class User < ApplicationRecord
validate :password_for_web, if: :mobile_device?
validate :password_confirmation_for_web, if: :mobile_device?
def password_for_web
if !app_user && password.blank?
errors.add(:password, "can't be blank.")
end
end
def password_confirmation_for_web
if !app_user && password != self.password_confirmation=
errors.add(:password_confirmation, "doesn't match.")
end
end
def mobile_device?
mobile_device.present?
end
end
You can also use gems to check mobile device like:
https://github.com/fnando/browser
https://github.com/shenoudab/active_device
If you have separate mobile app.
Add extra parameter in your registration form for mobile application like I used in my view with name mobile_device. Use updated model code and you are done.

RSpec pass validation test, which should be failed

I have a Company model with attr_accessor :administrator, so when user creates company, he also need to fill some fields for administrator of this company. I'm trying to test, that he fill all fields correctly.
class Company < ActiveRecord::Base
attr_accessor :administrator
validates :name, presence: true
validates :administrator, presence: true, if: :administrator_is_valid?
private
def administrator_is_valid?
administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]
end
end
company_spec.rb is:
require 'rails_helper'
describe Company do
it 'is valid with name and administrator' do
company = Company.new(name: 'Company',
administrator: {
name: nil,
email: nil,
phone: nil,
password: 'password',
password_confirmation: ''
})
expect(company).to be_valid
end
end
So, as you see, I have a lot of mistakes in validation test, but RSpec pass it.
Thanks!
That's because you didn't construct your validation properly. See, if: administrator_is_valid? will return false for your test, telling Rails to skip this validation rule.
I suggest you drop using the presence validator in favor of using administrator_is_valid? method as a validation method, because after all, if the administrator is valid then it is present. The code should look like this
validate :administrator_is_valid?
private
def administrator_is_valid?
(administrator[:name].present? and
administrator[:phone].present? and
administrator[:email].present? and
administrator[:password].present? and
administrator[:password_confirmation].present? and
administrator[:password] == administrator[:password_confirmation]) or
errors.add(:administrator, 'is not valid')
end
You could clean up your code like this:
validate :administrator_is_valid?
private
def administrator_is_valid?
if administrator_cols_present? && administrator_passwords_match?
true
else
errors.add(:administrator, 'is not valid')
end
end
def administrator_cols_present?
%w(name phone email password password_confirmation).all? do |col|
administrator[col.to_sym].present? # or use %i() instead of to_sym
end
end
def administrator_passwords_match?
administrator[:password] == administrator[:password_confirmation]
end
Another improvement might be to move your administrator to a struct, then call valid? on the object.
admin = Struct.new(cols) do
def valid?
cols_present? && passwords_match?
end
def cols_present?
cols.values.all? { |col| col.present? }
end
def passwords_match?
cols[:password] == cols[:password_confirmation]
end
end
Then:
validate :administrator_is_valid?
def admin_struct
#admin_struct ||= admin.new(administrator)
end
def administrator_is_valid?
errors.add(:administrator, 'is not valid') unless admin_struct.valid?
end

Adding a validation error with a before_save callback or custom validator?

I have a model Listing that belongs_to :user. Alternatively, User has_many :listings. Each listing has a category field that classifies it (dogs, cats, etc). The User also has a boolean field called is_premium.
Here is how I am validating the category...
validates_format_of :category,
:with => /(dogs|cats|birds|tigers|lions|rhinos)/,
:message => 'is incorrect'
Let's say I only want to allow premium users to be able to add tigers, lions, and rhinos. How would I go about this? Would it be best to do it in a before_save method?
before_save :premium_check
def premium_check
# Some type of logic here to see if category is tiger, lion, or rhino.
# If it is, then check if the user is premium. If it's not, it doesn't matter.
# If user isn't premium then add an error message.
end
Thanks in advance!
class Listing < ActiveRecord::Base
validate :premium_category
private
def premium_category
if !user.is_premium && %w(tigers lions rhinos).include?(category))
errors.add(:category, "not valid for non premium users")
end
end
end
If you want to add validation errors doing the before_save you could raise an exception then add the error in the controller like this:
class Listing < ActiveRecord::Base
before_save :premium_category
private
def premium_category
if !user.is_premium && %w(tigers lions rhinos).include?(category))
raise Exceptions::NotPremiumUser, "not valid for non premium users"
end
end
end
Then in your controller do something like:
begin
#listing.update(listing_params)
respond_with(#listing)
rescue Exceptions::NotPremiumUser => e
#listing.errors.add(:base, e.message)
respond_with(#listing)
end
Then in your /lib folder add a class like this:
module Exceptions
class NotPremiumUser < StandardError; end
end
But in your case I think using the validate method is a better solution.
Cheers,
You can use validates_exclusion_of:
validates :category, :exclusion => {
:in => ['list', 'of', 'invalid'],
:message => 'must not be premium category',
:unless => :user_is_premium?
}
protected
def user_is_premium?
self.user.premium?
end

Organic Rails Data-Model with NoSQL Database

I'd like to make full use of the organic character of a NoSQL document and build a dynamic data model which can grow, be changed, and is different for most datasets. Below is the model SomeRequest.rb with the code to set and get from Couchbase, but I can't get the function addOrUpdate(key, value) to work:
undefined method `each' for "0":String
Completed 500 Internal Server
Error in 16ms NoMethodError (undefined method `each' for "0":String):
config/initializers/quiet_assets.rb:7:in `call_with_quiet_assets'
Is the returning error. Is there a way to make this work, to add (or update existing) keys and save the document to the database afterwards?
class SomeRequest < Couchbase::Model
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Callbacks
extend ActiveModel::Naming
# Couch Model
define_model_callbacks :save
attribute :session_id
attribute :views, :default => 0
attribute :created_at, :default => lambda { Time.zone.now }
# iterate through attr keys and set instance vars
def initialize(attr = {})
#errors = ActiveModel::Errors.new(self)
unless attr.nil?
attr.each do |name, value|
setter = "#{name}="
next unless respond_to?(setter)
send(setter, value)
end
end
end
def addOrUpdate(key, value)
self[key] = value
end
def save
return false unless valid?
run_callbacks :save do
Couch.client.set(self.session_id, self)
end
true
end
def self.find(key)
return nil unless key
begin
doc = Couch.client.get(key)
self.new(doc)
rescue Couchbase::Error::NotFound => e
nil
end
end
end
Why don't you like to use find, save and create methods from couchbase-model gem?
class Couchbase::Error::RecordInvalid < Couchbase::Error::Base
attr_reader :record
def initialize(record)
#record = record
errors = #record.errors.full_messages.join(", ")
super("Record Invalid: #{errors}")
end
end
class SomeRequest < Couchbase::Model
include ActiveModel::Validations
attribute :session_id
attribute :views, :default => 0
attribute :created_at, :default => lambda { Time.zone.now }
validates_presence_of :session_id
before_save do |doc|
if doc.valid?
doc
else
raise Couchbase::Error::RecordInvalid.new(doc)
end
end
def initialize(*args)
#errors = ActiveModel::Errors.new(self)
super
end
end
And you might be right, it worth to add validation hooks by default, I think I will do it in next release. The example above is valid for release 0.3.0
What considering updateOrAdd I recommend you just use method #save and it will check if the key is persisted (currently by checking id attribute) and if the record doesn't have key yet, it will generate key and update it.
Update
In version 0.4.0 I added validation hooks into the gem, so the example above could be rewritten simpler.
class SomeRequest < Couchbase::Model
attribute :session_id
attribute :views, :default => 0
attribute :created_at, :default => lambda { Time.zone.now }
validates_presence_of :session_id
end

validation for models in ruby on rails

I have a model:
class HelloRails < ActiveRecord::Base
attr_accessible :filename, :filevalidate
include In4systemsModelCommon
validates :filename, presence: true
def update
parameters = [self.filename, #current_user]
parameters.map!{|p| ActiveRecord::Base.connection.quote p}
sql = "call stored_procedure(#{parameters.join(',')})"
ActiveRecord::Base.connection.execute(sql)
end
end
In the view I have a text_field called as :filename. When I click the submit button it calls this update method in model to call the stored proc to execute. Now validations are not working.
I dont want to accept nil for filename. How can I do this?
It doesn't validate because you are executing sql directly:
ActiveRecord::Base.connection.execute(sql)
Validations are only run when you use the "normal" ActiveRecord methods like save and update_attributes. To run validations manually you can call valid? on your object.
Model:
def update
return false unless self.valid? # validation failed: return false
parameters = [self.filename, #current_user]
parameters.map!{|p| ActiveRecord::Base.connection.quote p}
sql = "call stored_procedure(#{parameters.join(',')})"
ActiveRecord::Base.connection.execute(sql)
true
end
Then in your controller you have to check wether #object.update returns true or false and display errors in your view if necessary.
Controller:
# This is just an example. I don't know your code.
def update
#object = HelloRails.find(params[:id])
if #object.update
redirect_to somewhere
else
render :action => 'edit'
end
end

Resources