How do I force a rails ActiveRecord string to only accept strings?
Ruby is a duck language and has no type mismatches. If the object responds to to_s
, Rails will call on it to_s
and place it in the string field.
It sounds like you really want to check the format of the value you put in the field (is a string a "2"
valid value?), Which is what it is for validates_format_of
. For example, if you want to make sure that at least one non-digit character has a value, you can do this:
class Post < ActiveRecord::Base
validates_format_of :title, with: /\D/
end
Of course, you really want to tweak this regex a bit.
If you really can't stand the duck printing and want to make sure that only the true string is in that column, you can override title=
:
class Post < ActiveRecord::Base
def title=(value)
throw "`title' must be a String" unless value.is_a?(String)
super
end
end
Note that this will not be called when you do this for example. post[:title] = ...
or post.write_attribute(:title, ...)
, but it should cover 95% of situations.
source to share
This really goes against what Ruby is. If it is a string column, the database layer will render this value as a string and store it.
The concept of type is different from Ruby than in other languages. Generally, if an object is supported to_s
, then it is considered strict enough to be used, the basic tenet of Duck Typing .
If you pass in something that cannot be represented as a string, you will get an exception. For example:
post.title = Post.find(5)
If you want your fields to be non-numeric, you must add validation that enforces a specific format.
source to share
A new gem has been created to help validate types in rails and an explanatory blog post exists to answer the "why" question that was created in the first place.
In this library, your code will be simple:
class Post < ActiveRecord::Base
validates_type :title, :string
end
This will throw an exception when an integer value is assigned title
instead of quietly assigning to the title
string and saving it.
source to share
You can define a custom validator.
validates :title, type: String
and then:
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
value.class == options[:with]
end
end
source to share