In ActiveRecord, if you put a character string in a Date type or Integer type column, it will be automatically converted to that column type.
For example, if you have the following users table: (Created_at and updated_at are omitted for simplicity.)
create_table "users", force: :cascade do |t|
t.string "name"
t.integer "age"
end
Let's try entering various values using the Rails console.
irb (main): 001: 0> User.new (name: "Taro", age: 21) Try putting a number in #age
=> # <User name: "Taro", age: 21>
irb (main): 002: 0> User.new (name: "Taro", age: "21") Try putting a character string in #age
=> # <User name: "Taro", age: 21>
irb (main): 003: 0> User.new (name: "Taro", age: "21") Try putting a character string in #age (1 is a full-width number)
=> # <User name: "Taro", age: 2>
The first is a numerical value. Since it is a numerical value, it can be entered as it is.
The second is a string. The string "21" has been converted to the number 21. Wonderful.
How about the third one, the 1 part of "21" is a full-width number. It has become age: 2. It's completely different from 21.
By the way, even if you use "2 1" (with a space between them) or "2 a" (with a character other than a number), it will be age: 2.
Suppose you try to play a non-numeric input and apply the following validation.
validates :age, format: { with: /\A[0-9]+\z/}
In this case, if the user inputs "2 1", "2 a", etc., validation will be applied to 2 of age, and it will be saved as correct data.
The solution is to add _before_type_cast to age to validate the value entered by the user instead of the value after the type has been converted. [* Reference "before_type_cast with rails / validates"](https://dora.bk.tsukuba.ac.jp/~takeuchi/?%E3%82%BD%E3%83%95%E3%83%88%E3 % 82% A6% E3% 82% A7% E3% 82% A2% 2Frails% 2Fvalidates% E3% 81% A7before_type_cast)
validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true
In addition to validation, _before_type_cast is also useful when you want to convert alphanumeric characters in a character string from full-width to half-width, or when you want to save after removing spaces.
As an example, the following code uses a gem called Moji to convert from full-width to half-width.
This time, I use _before_type_cast because I want to align the input that is a mixture of full-width and half-width such as "21" (1 is full-width) of the user to half-width before validation.
After receiving the character string as it is input by the user with age_before_type_cast and converting it, the converted character string is put in age.
validates :age_before_type_cast, format: { with: /\A[0-9]+\z/ }, presence: true
before_validation do
string = self.send(:age_before_type_cast)
string = Moji.normalize_zen_han(string)
send(:write_attribute, :age, string)
end
First is the difference between normal attributes and attributes with _before_type_cast. The value cannot be obtained with [: 〇〇_before_type_cast]. In particular
irb (main): 001: 0> user = User.new (name: "Taro", age: "21")
irb(main):002:0> user.age
=> 21
irb(main):003:0> user.age_before_typecast
=> "21"
irb(main):004:0> user[:age]
=> 21
irb(main):005:0> user[:age_before_typecast]
=> nil
You can get the value with user.age_before_type_cast, When I try to get the value using user [: age_before_type_cast], nil is returned.
irb(main):001:0> user.age = 21
=> 21
irb(main):002:0> user.age_before_type_cast = 21
=> #NoMethodError occurs.
Since _before_type_cast is for checking the input before type conversion to age, of course it is not possible to update itself.
Also, if you save, 〇〇 and 〇〇_before_type_cast will be the same. In particular,
irb (main): 001: 0> user = User.new (name: "Taro", age: "21")
irb(main):002:0> user.save
irb(main):003:0> user.age_before_typecast
=> 21
The number 21 is returned instead of the string "21".
Be careful when implementing or testing because of the above properties!
[before_type_cast with rails / validates](https://dora.bk.tsukuba.ac.jp/~takeuchi/?%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6 % E3% 82% A7% E3% 82% A2% 2Frails% 2Fvalidates% E3% 81% A7before_type_cast)