seed_fuとseedについて調べてみた
はじめに
seed
とseed_fu
はRailsにおいてデータベースの初期データを投入するために使われる機能ですが、微妙に違いがあるようですので備忘録としてまとめておきたいと思います。
主にseed_fuをメインにまとめています。
本記事では、seedとseed_fuに関する説明を簡単に解説していきます。
seed
seedはRailsに標準で組み込まれている機能です。
db/seeds.rb
ファイルに初期データを記述して、rails db:seed
コマンドで実行します。
seed
は基本的に初期データを一度だけ投入するためのもの
であり、再度実行すると既存のデータが削除
されて新しいデータが挿入されます。
seed_fu
seedがRailsに標準で組み込まれている機能に対し、seed_fuはgemになります。
seed_fu
はseed
と異なりデータベースに対して既存のデータを削除せずに初期データを追加できる
ため、データがある状態で再度実行しても問題ありません。
seed_fuはRailsアプリケーションのルートディレクトリのdb/fixturesディレクトリ内に配置されます。
db/fixturesディレクトリ内には、開発、テスト、本番など、各環境に対応するファイルを置くことができます。
├── db │ ├── fixtures │ │ ├── development │ │ │ ├── 001_user.rb │ │ │ ├── 002_post.rb │ │ │ └── ... │ │ ├── staging │ │ │ └── ... │ │ └── production │ │ └── ... │ │ └── ... │ └── migrate │ ├── 20230401000001_create_users.rb │ ├── ...
以下のような形式でUserのseedデータを投入するためのファイルを記述することができます。
# db/fixtures/development/001_user.rb User.seed( { id: 1, name: 'LUFFY' }, { id: 2, name: 'UTA' }, ) # db/fixtures/development/002_post.rb Post.seed( { id: 1, user: User.find(2) }, { id: 2, user: User.find(2) }, { id: 3, user: User.find(2) } )
上記の書き方でも可能ですが、以下の形式で記述することも可能です。
User.seed do |s| s.id = 1 s.name = 'LUFFY' end
rails db:seed_fu
コマンドで実行することでデータが投入されます。
特定のファイルを指定したい時
以下のようにすることでファイル名にマッチするシードファイルが実行されます。
実運用していてFIXTURE_PATH=が上手く動作しなかったことがありましたが以下のコマンドでは動作しました。
rails db:seed_fu FILTER=001,002
YAMLで記述することもできる
seedでもseed_fuでも共通していますがYAMLファイルを用いてデータの投入を行うことも可能のようです。
# db/fixtures/users.yml - name: John email: john@example.com - name: Jane email: jane@example.com
RubyファイルやYAMLファイルの他にも、CSVファイルやJSONファイルなどでも記述することができます。
所感としては記述が簡単なYAMLが良さそうと思いましたが、動的なデータを使用することが出来るRubyの方が柔軟に使えるため良いかもしれません。
seed_fuを使うべき理由
- データを更新できる
seed_fuは、更新されたデータを自動的に更新することができます。
seedでは、既存のデータを削除して作り直すため、開発する上での使い勝手が悪いです。
- データの妥当性を検証できる
seed_fu データの妥当性を検証して、不正なデータがあれば例外を発生させます。どこかのファイルに問題がある場合、実行はされず例外だけ発生するため気軽にコマンドを実行することができます。
seedでは、妥当性の検証を行うことができません。
- テーブルの依存関係を解決する
seed_fuでは、テーブル間の依存関係を自動的に解決し、依存関係に従ってデータを挿入することができます。
seedでは、テーブル間の依存関係を考慮してデータを挿入することができません。
GitHubのソース
おまけ的なところで /lib/seed-fu/seeder.rb こちらのソースコード気になって見ていたので書き残しておきます。
RailsのSeedデータを作成・更新するためのSeedingクラスが定義されていました。
require 'active_support/core_ext/hash/keys' module SeedFu # Creates or updates seed records with data. # # It is not recommended to use this class directly. Instead, use `Model.seed`, and `Model.seed_once`, # where `Model` is your Active Record model. # # @see ActiveRecordExtension class Seeder # @param [ActiveRecord::Base] model_class The model to be seeded # @param [Array<Symbol>] constraints A list of attributes which identify a particular seed. If # a record with these attributes already exists then it will be updated rather than created. # @param [Array<Hash>] data Each item in this array is a hash containing attributes for a # particular record. # @param [Hash] options # @option options [Boolean] :quiet (SeedFu.quiet) If true, output will be silenced # @option options [Boolean] :insert_only (false) If true then existing records which match the # constraints will not be updated, even if the seed data has changed def initialize(model_class, constraints, data, options = {}) @model_class = model_class @constraints = constraints.to_a.empty? ? [:id] : constraints @data = data.to_a || [] @options = options.symbolize_keys @options[:quiet] ||= SeedFu.quiet validate_constraints! validate_data! end # Insert/update the records as appropriate. Validation is skipped while saving. # @return [Array<ActiveRecord::Base>] The records which have been seeded def seed records = @model_class.transaction do @data.map { |record_data| seed_record(record_data.symbolize_keys) } end update_id_sequence records end private def validate_constraints! unknown_columns = @constraints.map(&:to_s) - @model_class.column_names unless unknown_columns.empty? raise(ArgumentError, "Your seed constraints contained unknown columns: #{column_list(unknown_columns)}. " + "Valid columns are: #{column_list(@model_class.column_names)}.") end end def validate_data! raise ArgumentError, "Seed data missing" if @data.empty? end def column_list(columns) '`' + columns.join("`, `") + '`' end def seed_record(data) record = find_or_initialize_record(data) return if @options[:insert_only] && !record.new_record? puts " - #{@model_class} #{data.inspect}" unless @options[:quiet] # Rails 3 or Rails 4 + rails/protected_attributes if record.class.respond_to?(:protected_attributes) && record.class.respond_to?(:accessible_attributes) record.assign_attributes(data, :without_protection => true) # Rails 4 without rails/protected_attributes else record.assign_attributes(data) end record.save(:validate => false) || raise(ActiveRecord::RecordNotSaved, 'Record not saved!') record end def find_or_initialize_record(data) @model_class.where(constraint_conditions(data)).take || @model_class.new end def constraint_conditions(data) Hash[@constraints.map { |c| [c, data[c.to_sym]] }] end def update_id_sequence if @model_class.connection.adapter_name == "PostgreSQL" or @model_class.connection.adapter_name == "PostGIS" return if @model_class.primary_key.nil? || @model_class.sequence_name.nil? quoted_id = @model_class.connection.quote_column_name(@model_class.primary_key) sequence = @model_class.sequence_name # TODO postgresql_version was made public in Rails 5.0.0, remove #send when support for earlier versions are dropped if @model_class.connection.send(:postgresql_version) >= 100000 sql =<<-EOS SELECT setval('#{sequence}', (SELECT GREATEST(MAX(#{quoted_id})+(SELECT seqincrement FROM pg_sequence WHERE seqrelid = '#{sequence}'::regclass), (SELECT seqmin FROM pg_sequence WHERE seqrelid = '#{sequence}'::regclass)) FROM #{@model_class.quoted_table_name}), false) EOS else sql =<<-EOS SELECT setval('#{sequence}', (SELECT GREATEST(MAX(#{quoted_id})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{@model_class.quoted_table_name}), false) EOS end @model_class.connection.execute sql end end end end
- 初期化時に、モデルクラス、制約、データ、オプションの4つのパラメータを取る
- モデルクラスは、Seedデータを作成・更新するためのActive Recordモデル
- 制約はSeedデータの一意性を確保するための属性のリスト
- Seedデータの属性情報を含むハッシュの配列
- quietとinsert_onlyの2つのプロパティ(コメントアウトに説明がある)
seed_record
メソッド- 与えられたデータに基づいてレコードを作成・更新するためのメソッド
find_or_initialize_record
メソッド- メソッドでレコードを探し、見つからなかった場合には新規に作成を行う
insert_only
オプションが有効な場合には、既存のレコードは更新されず、新しいレコードのみが作成される。
おわりに
コメント
本記事の内容は以上になります!
ふと気になったことがきっかけで気がついたら結構調べていたのでまとめました。
参考になったり学びのきっかけになりますと幸いです。
間違いがありましたら修正いたしますので、ご指摘ください。
興味があれば他の記事も更新していきますので是非ご覧になってください♪
◇ プログラミングスクールのご紹介 (卒業生より)
お世話になったプログラミングスクールであるRUNTEQです♪
ご不明な点ありましたらお気軽にコメントか、TwitterのDMでお答えします♪