I have a simple table for storing users accounts information (emails and passwords) with two additional columns:
is_active - says if user account is enable or disabled - the column
type is boolean and in the context of DB2 it is mapped with
decimal(1)
registration_date - says when the user was created - the column
type is datetime and in the context ofDB2 it is mapped with
datetime
As this fields will not be set by the user, I have deleted their inputs from the users _form.
I want to populated this fields in my users controller as follows:
def create
#security_user = SecurityUser.new(params[:security_user])
#security_user.is_active = 0
#security_user.registration_date = DateTime.now
...
end
But I can not pass the validations that I have in the model. They looks like:
class SecurityUser < ActiveRecord::Base
# Loading custom validators
require 'lib/date_format_validator'
...
# Accessible columns
...
# Relationships
...
# Validations
validates :is_active, inclusion: { in: 0..1, message: "only '0' and '1' allowed"}, presence: true
validates :registration_date, date_format:true , presence: true
end
where the 'lib/date_format_validator' looks like follows:
class DateFormatValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
if (DateTime.parse(value) rescue ArgumentError) == ArgumentError
object.errors[attribute] << (options[:message] || "is not valid datetime")
end
end
end
What I am doing wrong?
EDIT: The screenshot below displays the errors:
EDIT2: Sam Ruby's answer helps me to finish with something like this for my custom validator method:
class DateFormatValidator < ActiveModel::EachValidator
def validate_each(object, attribute, value)
unless value.kind_of? DateTime || (DateTime.parse(value) rescue ArgumentError) != ArgumentError
object.errors[attribute] << (options[:message] || "is not valid datetime")
end
end
end
and to transform the validates method for the is_active column as follows:
validates :is_active, inclusion: { in: [ true, false ], message: "only 'true' and 'false' allowed"}
because as it is said in the official documentation:
Since false.blank? is true, if you want to validate the presence of a boolean field you should use validates :field_name, :inclusion => { :in => [true, false] }.
The problem is that you are trying to validate the ActiveRecord object as if the columns are of type String. But since you have defined your columns as boolean and datetime, what you will be validating will be of type TrueClass, FalseClass or ActiveSupport::TimeWithZone.
In other words, the values are already parsed.
true is never 0 or 1.
DateTime.parse(DateTime.now) will always raise ArgumentError
If you want to validate the unparsed values, do so in the controller.
Related
I have a SaaS application where an Account has many Users.
In an Account, the account owner can specify certain filters on the users that are applying to be part of the account. For instance, he can set that these requirement conditions must be met in order for user to be accepted into account : user.age > 21, user.name.count > 4.
I was thinking of creating a FilterCondition model, which belongs to Account, where a row could look like account_id: 1, attribute: "age", condition_string: "> 21" or account_id: 1, attribute: "phone_type", condition_string: "== iphone"
and then when I want to only accept users that meet these conditions do something like
#User.rb
def passes_requirements?
account.filter_conditions.each do |filter_conditions|
attr = filter_condition.attribute
return false if self.attr != filter_condition.condition
end
true
end
I know that is completely wrong syntax, but it should get the point across on what I would like to do.
Any suggested ways to allow accounts to save requirement conditions, and then check on Users to see if they meet those requirements?
It will be easier if the condition string is separated into a comparator (e.g >) and the value to be compared:
class FilterCondition < ActiveRecord::Base
belongs_to :account
validates :attribute, presence: true
validates :comparator, presence: true
validates :value, presence: true
def matching_users(query_chain = User.where(account: account))
query_chain.where("#{attribute} #{safe_comparator} ?", value)
end
private
def safe_comparator
safe_values = ['=', '>', '>=', '<', '<='] # etc
return comparator if safe_values.include? comparator
''
end
end
The safe_comparator method reduces the risk of SQL injection into the query. Chaining a collection of filters is a bit tricky, but something like the following idea should work.
class Account < ActiveRecord::Base
#....
has_many :filter_conditions
def filtered_users
query = User.where(account: self)
filter_conditions.each do |filter_condition|
query = filter_condition.matching_users(query)
end
query
end
end
account = Account.first
filter_1 = FilterCondition.create(
account: account,
attribute: :age,
comparator: '>=',
value: 21
)
filter_2 = FilterCondition.create(
account: account,
attribute: :age,
comparator: '<=',
value: 99
)
account.filtered_users
It is possible have in model for example user model
class User < ActiveRecord::Base
attr_accessible :number
validates_length_of :number, :is => 4
...
end
validation on length 4 (1234) with one exception that number can be value 0 ? :-)
i was looking to documentation here http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html but i dont know how to do it ?
edit: now i realized that maybe regexp can be used, but thats not my strong field :-p
You probably want to allow length of zero to prepare for the case the input is nil or blank.
There is built-in simple solution for such case
validates :number, length { :is => 4 }, allow_blank: true
# allow_blank includes cases of both nil and blank
Done.
Doc: http://guides.rubyonrails.org/active_record_validations.html#allow-blank
If the number comes in as a string you can validate the format of it with a regex to achieve something close:
class User < ActiveRecord::Base
attr_accessible :number
validates :number, :format => { :with => /^(\d{4}|0{1})$/ }
...
end
This says to validate the format of the number (assuming it is a string) such that from the beginning of the string there is either a pattern of 4 digits or a single 0 digit followed by the end of the string.
i have a model called Fund and a model called Company .. where fund belongs_to a company.
i have this validation in my Fund table:
validates :name, presence: true, uniqueness: true
This works both on server side and client side using client_side_validations. But i want my fund names to be unique across both fund.name values and fund.company.name values. And i want to do it in a way it would work with client_side_validations too.
Suggestions?
Ended up creating a very specific validator and adding it to client-side-validation. Here'z the breakdown
In models/fund.rb
validates_fund_name_not_company_name :name
new file in config/initializers/validators .. called fund_name_not_company_name_validator.rb
class FundNameNotCompanyNameValidator < ActiveModel::EachValidator
def validate_each(record, attr_name, value)
if ::Company.exists?(name: value)
record.errors.add(attr_name, :fund_name_not_company_name, options.merge(:value => value))
end
end
end
# This allows us to assign the validator in the model
module ActiveModel::Validations::HelperMethods
def validates_fund_name_not_company_name(*attr_names)
validates_with FundNameNotCompanyNameValidator, _merge_attributes(attr_names)
end
end
module ClientSideValidations::Middleware
class FundNameNotCompanyName < ClientSideValidations::Middleware::Base
def response
if ::Company.exists?(name: request.params[:name])
self.status = 404
else
self.status = 200
end
super
end
end
end
then in app/assets/javascripts/rails.validations.custom.js
clientSideValidations.validators.remote['fund_name_not_company_name'] = function(element, options) {
if ($.ajax({
url: '/validators/fund_name_not_company_name',
data: { name: element.val() },
// async must be false
async: false
}).status == 404) { return options.message; }
}
This helped a great deal
Say I have an assets model that contains two columns:
Assets
ownership:string
lease_id:integer # My Lease Object
So ownership can either be "OWN" or "LEASE".
Now, I only want to allow a lease_id if ownership is LEASE and if ownership happens to be LEASE then I want to require a lease object.
How can this be done in Rails 3.2.2?
You can add optional validations based on a method which returns true/false. Keep in mind that this will only enforce that a lease_id is present when ownership == "LEASE". This will not restrict a lease_id from being added in any case.
class Asset < ActiveRecord::Base
validates :lease_id, presence: { :if => :lease? }
def lease?
self.ownership == "LEASE"
end
end
If you want to restrict a lease_id altogether, you can use a callback to remove the property before the object is saved to the DB.
class Asset < ActiveRecord::Base
before_create :restrict_lease_id
def restrict_lease_id
lease_id = nil if self.ownership == "LEASE"
end
end
Here's my test:
require 'spec_helper'
describe League do
it 'should default weekly to false' do
league = Factory.create(:league, :weekly => nil)
league.weekly.should == false
end
end
end
And here's my model:
class League < ActiveRecord::Base
validates :weekly, :inclusion => { :in => [true, false] }
before_create :default_values
protected
def default_values
self.weekly ||= false
end
end
When I run my test, I get the following error message:
Failure/Error: league = Factory.create(:league, :weekly => nil)
ActiveRecord::RecordInvalid:
Validation failed: Weekly is not included in the list
I've tried a couple different approaches to trying to create a league record and trigger the callback, but I haven't had any luck. Is there something that I am missing about testing callbacks using RSpec?
I believe that what you are saying is, before create, set weekly to false, then create actually sets weekly to nil, overwriting the false.
Just do
require 'spec_helper'
describe League do
it 'should default weekly to false' do
league = Factory.create(:league) # <= this line changed
league.weekly.should == false
end
end
end
in your test. No need to explicitly set nil.