以下の内容はhttps://blog.kamipo.net/entry/2021/01/06/144407より取得しました。


activerecord-importを利用して無効なデータを無理やりINSERTする

activerecord-importと:on_duplicate_key_ignoreオプションを組み合わせるとカラム定義の範囲外の値であっても無理やりINSERTすることができます。

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "activerecord", "6.1.0"
  gem "activerecord-import"
  gem "mysql2"
end

require "active_record"
require "logger"

ActiveRecord::Base.establish_connection(adapter: "mysql2", database: "test", username: "root")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :name, index: { unique: true }
    t.decimal :money, precision: 10
  end
end

class User < ActiveRecord::Base
end

attributes = [
  { name: "foo", money: "10000000000" },
  { name: "foo", money: "20000000000" },
]

User.import(attributes, on_duplicate_key_ignore: true)

# User.insert_all(attributes)

puts
puts User.pluck(:money) # => 9999999999
puts
% ruby foo.rb
Fetching gem metadata from https://rubygems.org/..............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using concurrent-ruby 1.1.7
Using bundler 2.2.3
Using minitest 5.14.3
Using zeitwerk 2.4.2
Using mysql2 0.5.3
Using i18n 1.8.7
Using tzinfo 2.0.4
Using activesupport 6.1.0
Using activemodel 6.1.0
Using activerecord 6.1.0
Using activerecord-import 1.0.7
-- create_table(:users, {:force=>true})
D, [2021-01-06T14:04:10.296375 #81282] DEBUG -- :    (66.5ms)  DROP TABLE IF EXISTS `users`
D, [2021-01-06T14:04:10.377990 #81282] DEBUG -- :    (80.7ms)  CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255), `money` decimal(10), UNIQUE INDEX `index_users_on_name` (`name`))
   -> 0.1967s
D, [2021-01-06T14:04:10.397652 #81282] DEBUG -- :   ActiveRecord::InternalMetadata Load (0.5ms)  SELECT `ar_internal_metadata`.* FROM `ar_internal_metadata` WHERE `ar_internal_metadata`.`key` = 'environment' LIMIT 1
D, [2021-01-06T14:04:10.417055 #81282] DEBUG -- :    (0.3ms)  SELECT @@max_allowed_packet
D, [2021-01-06T14:04:10.424171 #81282] DEBUG -- :   User Create Many (6.8ms)  INSERT IGNORE INTO `users` (`name`,`money`) VALUES ('foo',10000000000),('foo',20000000000)

D, [2021-01-06T14:04:10.428391 #81282] DEBUG -- :    (3.4ms)  SELECT `users`.`money` FROM `users`
9999999999

https://gist.github.com/kamipo/3db82c3bb7cbcbf007b4d4367a5c5227

これはどういう原理かというと、activerecord-importではINSERTしたいけどすでに(ユニークキーが)おなじレコードがあるときはスルーしたい(i.e. on_duplicate_key_ignore)という機能を実現するのにMySQLではINSERT IGNORE構文を使っていて、INSERT IGNOREではINSERT中のすべてのエラーを無視して無効な値は可能ならもっとも近い値に調整してINSERTするという振る舞いをするため、このような挙動を引き起こすことができます。

では、INSERTしたいけどすでに(ユニークキーが)おなじレコードがあるときはスルーしたい、けど無効な値はちゃんとエラーにしてほしいときはどうしたらいいかというと、Rails 6.0から使えるinsert_allというバルクインサート用のAPIを使うことができます。RailsチームではわいがMySQLチョットデキルので、この問題についてはレビューでフィードバックして対処されており安心してご利用になることができます。

github.com

See also

songmu.jp

それでは本年も引き続きよろしくおねがいいたします。




以上の内容はhttps://blog.kamipo.net/entry/2021/01/06/144407より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14