Is there a way to limit the number of lines / text rows in a type text attribute in Rails?
I know that I can limit the number of characters like so:
validates :message, :length => { :maximum => 100 }
But how about the number of lines?
Thanks for any help.
You could create a custom validator to implement the required logic. Something along the following idea should do the trick:
class LinesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
lines = value.split("\n")
if lines.size > options[:maximum]
record.errors[attribute] << "too many lines"
end
if lines.any? { |l| l.size > options[:length]}
record.errors[attribute] << "line longer than allowed length"
end
end
end
And the usage could be as follows:
validates :message, lines: { maximum: 5, length: 10}
For more information regarding validations and custom validators, you can read the rails docs
Writing a custom validation to do this is pretty easy. You use validate (not validates) to do this:
validate :not_too_many_lines
private
def not_too_many_lines
if self.message.split("\n").length > 10
self.errors.add :message, "has too many lines"
end
end
Under the hood this runs before valid?, which checks to see whether errors has anything in it. If you then ran <record>.errors.full_messages after trying to save an invalid record you'd see "Message has too many lines". If you just want it to say "too many lines" you can use self.errors.add :base, "too many lines"
I can't comment yet.
So to add top the answer, split('\n') is not always safe.
Better just use String#lines
Here is the example with the lines method instead.
validate :not_too_many_lines
private
def not_too_many_lines
if self.message.lines.count > 10
errors.add :message, "has too many lines"
end
end
Related
I am using PhoneLib (https://github.com/daddyz/phonelib) to validate my phone numbers.
I have a model Subscription with a column phone_number.
I'm testing this phone number: 3052612151.
I can see that Phonelib considers it to be a valid number:
> Phonelib.valid_for_country? "3052612151", :us
#=> true
However, when I use Phonelib's validator, my record is not considered to be valid.
class Subscription < ApplicationRecord
validates :email, presence: true
validates :phone_number, phone: { countries: :us }
> s = Subscription.create(phone_number: '3052612151', email: 'example#example.com')
> s.valid?
#=> false
> s.errors.full_messages
#=> ["Phone number is invalid"]
I've been scratching my head for over an hour. Can anyone see what I've done wrong? Why does Phonelib consider the number to be valid when calling #valid_for_country? but not when used as a validator?
The only thing I can think of is maybe you should set the default country in an initializer
# config/initializers/phonelib.rb
Phonelib.default_country = "US"
Then just use validates :phone_number, phone: true in your model.
Try using countries: [:us], the validator actually does a & operation and checks if the size is greater than 0, maybe it's expecting an array. https://github.com/daddyz/phonelib/blob/master/lib/validators/phone_validator.rb#L85
The validator actually checks 4 validations, it's a really bad design that all validations return the same error message, weird :S. https://github.com/daddyz/phonelib/blob/master/lib/validators/phone_validator.rb#L63
I just did set up validations for User model. The codes in my User model are just like this.
If I'd like to add maximum length validation for each tag, what should I add to this?
I also would like to know shorter way to code those whole validations if possible.
acts_as_taggable_on :tags
validates_presence_of :tag_list,
:message => "Create at least 1 tag"
validates_size_of :tag_list,
:maximum => 4,
:message => '4 tags maximum'
validate :max_tag_size # HERE WITH VALIDATE - NOT VALIDATES!
def max_tag_size
errors[:tag_list] << "4 tags maximum" if self.tag_list.split(",").count > 4
errors[:tag_list] << "tag lenght < 10 letters " if self.tag_list.split(",").collect(&:length).max >= 10
end
I tried many things, and finally I found out how to solve!
This made it working fine:)
validate :max_tag_size
def max_tag_size
errors[:tag_list] << "2 tags maximum" if tag_list.count > 2
self.tag_list.each do |tag|
errors[:tag_list] << "#{tag} must be shorter than 10 characters maximum" if tag.length > 10
end
end
I have a rails question that I have been unable to find an answer for on my own. I apologize if it's very simple or obvious, I'm a complete beginner here.
So I have a column in my db called :client_code, which is defined in the model as the down-cased concatenation of the first character of :first_name, and :last_name. So for instance, if I pull up a new form and enter 'John' for :first_name, and 'Doe' for :last_name, :client_code is automatically given the value of 'jdoe'. Here's the relevant portion of the model code:
class Client < ActiveRecord::Base
before_validation :client_code_default_format
validates_presence_of :first_name, :last_name, :email
validates_uniqueness_of :client_code
...
def client_code_default_format
self.client_code = "#{first_name[0]}#{last_name}".downcase
end
end
I would like to add something to this code so that, in the event that someone enters a different client with the same exact name, it does't fail the uniqueness validation but instead creates a slightly modified :client_code ('jdoe2', for example). I could probably figure out how to add an index to all of them, but I would prefer to only include numbers as a failsafe in the case of duplicates. Can anyone point me in the right direction?
Calculating the number of current matching Client objects with the same client_code should work
def client_code_default_format
preferred_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = #{preferred_client_code}")
self.client_code = count == 0 ? preferred_client_code : preferred_client_code + count
end
Many thanks to #Dieseltime for his response. I took his suggestion and was able to get the functionality I wanted with some minor changes:
before_validation :format_client_code
validates_presence_of :first_name, :last_name, :email, :company_id
validates_uniqueness_of :client_code
...
def format_client_code
unless self.client_code != nil
default_client_code = "#{first_name[0]}#{last_name}".downcase
count = Client.count(:conditions => "client_code = '#{default_client_code}'")
if count == 0
self.client_code = default_client_code
else
self.client_code = default_client_code + (count + 1).to_s
end
end
end
I use this validation:
validates_numericality_of :price, :greater_than_or_equal_to => 0, :less_than => 1000000
How could I set a different :message for each one of the following cases ?
price < 0
price >= 1000000
Assuming you're using Rails 3, another option you have is to create a custom validator:
# You can put this in lib/better_numericality_validator.rb
class BetterNumericalityValidator < ActiveModel::EachValidator
def validate_each(record,attribute,value)
if value < 0
record.errors[attribute] << "must be greater than or equal to 0"
elsif value >= 1000000
record.errors[attribute] << "must be less than 1000000")
end
end
end
Then you can use your custom validator in your model:
# In your model.rb
validates :price, :better_numericality => true
This method is very similar to Anubhaw's answer. But pulling the logic out into the a custom validator makes it so that you can reuse the validation elsewhere easily, you can easily unit test the validator in isolation, and I personally think that validates :price, :better_numericality => true leaves your model looking cleaner than the alternative.
You can use following in model.rb:-
def validate
if self.price < 0
errors.add(:price, "custom message")
elsif self.price > 1000000
errors.add(:price, "custom message")
end
end
Thanks....
How about:
validates_numericality_of :price, :greater_than_or_equal_to => 0, :message => "Foo"
validates_numericality_of :price, :less_than => 1000000, :message => "Bar"
I've not tested it, but it should work?
Alternatively, Anubhaw's question is a good fallback.
At some point, you should probably ask yourself whether it isn't time to apply some convention over configuration.
In my opinion, an error message such as "Please enter a valid price greater than 0 and less than 1 million" (or similar) is a perfectly valid solution to the problem. It prevents you from adding unnecessary complexity to your application and allows you to move on to other (presumably more important) features.
I'm answering my own questions - just putting this up here for google-fu in case it helps someone else. This code allows you to validate the presence of one field in a list. See comments in code for usage. Just paste this into lib/custom_validations.rb and add require 'custom_validations' to your environment.rb
#good post on how to do stuff like this http://www.marklunds.com/articles/one/312
module ActiveRecord
module Validations
module ClassMethods
# Use to check for this, that or those was entered... example:
# :validates_presence_of_at_least_one_field :last_name, :company_name - would require either last_name or company_name to be filled in
# also works with arrays
# :validates_presence_of_at_least_one_field :email, [:name, :address, :city, :state] - would require email or a mailing type address
def validates_presence_of_at_least_one_field(*attr_names)
msg = attr_names.collect {|a| a.is_a?(Array) ? " ( #{a.join(", ")} ) " : a.to_s}.join(", ") +
"can't all be blank. At least one field (set) must be filled in."
configuration = {
:on => :save,
:message => msg }
configuration.update(attr_names.extract_options!)
send(validation_method(configuration[:on]), configuration) do |record|
found = false
attr_names.each do |a|
a = [a] unless a.is_a?(Array)
found = true
a.each do |attr|
value = record.respond_to?(attr.to_s) ? record.send(attr.to_s) : record[attr.to_s]
found = !value.blank?
end
break if found
end
record.errors.add_to_base(configuration[:message]) unless found
end
end
end
end
end
This works for me in Rails 3, although I'm only validating whether one or the other field is present:
validates :last_name, :presence => {unless => Proc.new { |a| a.company_name.present? }, :message => "You must enter a last name, company name, or both"}
That will only validate presence of last_name if company name is blank. You only need the one because both will be blank in the error condition, so to have a validator on company_name as well is redundant. The only annoying thing is that it spits out the column name before the message, and I used the answer from this question regarding Humanized Attributes to get around it (just setting the last_name humanized attribute to ""