11

Here is my schema.rb

  create_table "users", force: true do |t|
    t.string   "name",       limit: 6
    t.string   "email"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

I set the limit fo string for the column "name".

Then, in console:

 user = User.new(name:"1234567890",email:"username@gmail.com")
 user.save!

It raised the error:

ActiveRecord::StatementInvalid: Mysql2::Error: Data too long for column 'name' at row 1: INSERT INTO `users` (`created_at`, `email`, `name`, `updated_at`) VALUES ('2014-06-19 15:08:15', 'username@gmail.com', '1234567890', '2014-06-19 15:08:15')

But, when I switched to rails 3.

I found it truncated the string "1234567890" automatically, and inserted "123456" into database without error.

Is there anything about this has been removed in rails 4?

Should I add some truncate functions in the model by myself? Thanks!

tzzzoz
  • 336
  • 1
  • 4
  • 12
  • 1
    For the last question, it may depend on your use case for `User` creation. Maybe it might be better to add a validator to check for length on `:name` and display an error to the user if it ends up too long? – Paul Richter Jun 19 '14 at 15:26
  • Validation is probably the way to go, because a user should know that their name can't be longer than 6 characters. Otherwise they might be surprised when they enter "Jonathan" but when they look at their profile their name is listed as "Jonath". – Joe Kennedy Jun 19 '14 at 15:31
  • Thanks, I understand the better way is to add some validation. But I just want to figure out what was changed in Rails 4, and I have some models are invisible to users, so in this case, validation can't help me out. – tzzzoz Jun 19 '14 at 15:38
  • I see. It looks like from what you said that is has changed in Rails 4... The way to go would probably be to truncate it in a `before_save` callback function if the column isn't visible to the user. – Joe Kennedy Jun 19 '14 at 15:40

3 Answers3

21

What you're seeing is a difference in MySQL, not Rails. By default, MySQL will truncate data that's too long rather than throwing an error. If you set MySQL to strict mode, it will throw errors instead of silently truncating data.

With version 4, Rails turns on strict mode by default. That's why you're seeing different behavior with Rails 3. This is the behavior you probably want. Silently truncating data is almost always bad and can lead to very confusing behavior for users.

If you really want to truncate the data, you could turn off strict mode or use a before filter:

before_save :truncate_username
def truncate_username
  self.username = username.slice(0, 6)
end
Community
  • 1
  • 1
James Mason
  • 4,246
  • 1
  • 21
  • 26
  • Thanks, it's helpful! Lifesaver – tzzzoz Jun 20 '14 at 08:30
  • 1
    Truncating upon character length is not enough if the content has non-ASCII chars like `ç`. Rails 6 added String#truncate_bytes to truncate strings to a maximum bytesize without breaking multibyte characters or grapheme clusters – Peter Toth Mar 04 '21 at 08:46
8

I stumbled across this article coincidently written only a few days ago.

The main point of interest is this:

...I recently upgraded from rails 3.2 to rails 4.0. They implemented a major change with ActiveRecords that I can find no mention of any where except in the source and change log.

  • mysql and mysql2 connections will set SQL_MODE=STRICT_ALL_TABLES by default to avoid silent data loss. This can be disabled by specifying strict: false in your database.yml.

This would seem to explain why you stopped receiving the error when you reverted back to Rails 3. You can see this in the options of the MySQL connection adapter module, and it looks like it was added way back in May 2012 for the 4.1.2 release candidate (if I'm reading the tags correctly).

This person solved their issue by ...[fixing] code to either have the proper field lengths, or manually truncate the data....

In your case, you might be able to solve your problem in Rails 4 simply by adding strict: false in your database.yml. If you want to customize how the data is truncated, I agree with JKen13579's suggestion about the before_save callback. Otherwise, from what I can see, it appears to truncate the right-most characters, so if that is sufficient, you can probably get away with the default truncation behaviour.

Paul Richter
  • 10,908
  • 10
  • 52
  • 85
  • 1
    This answer not only points to an article that actually explains what the real problem is, after reading it you can also see why just slicing data yourself on the migration is just as bad. Great answer @paul-richter – josethernandezc Feb 22 '16 at 18:16
2

I learned about setting strict: false in database.yml too late. After trying to do various things like set maxlength 255 on all inputs, I have a workaround. This will probably have a performance impact on heavy write workloads. Fine for your average CMS.

Dumping this code here in case, for whatever reason, you can't disable the strict mode in mysql now or in the future.

Basically, before_validation - if the value is dirty and varchar and > 255, trim it.

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  before_validation :silent_string_truncate
  def silent_string_truncate
    # For each changed value
    self.changes.each do |key,values|
      # Only worry about non-empty fields, which are longer than 255
      if values.last.blank? || values.last.length < 255
        next
      end

      # And only truncate if its a column on this table
      if self.class.columns_hash[key].blank?
        next
      end

      # And only truncate on string fields (varchar)
      properties = self.class.columns_hash[key]
      if properties.type == :string
        self.send("#{key}=", values.last.slice(0,255))
      end

    end
  end

  # Other global methods here
end
dyson returns
  • 3,376
  • 4
  • 16
  • 20