{"id":25872096,"url":"https://github.com/aktsk/seed_express","last_synced_at":"2025-03-02T07:37:50.556Z","repository":{"id":16844747,"uuid":"19604527","full_name":"aktsk/seed_express","owner":"aktsk","description":null,"archived":false,"fork":false,"pushed_at":"2017-12-26T10:14:49.000Z","size":205,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":100,"default_branch":"develop","last_synced_at":"2024-03-26T17:57:11.122Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aktsk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-05-09T08:32:51.000Z","updated_at":"2017-10-27T05:14:50.000Z","dependencies_parsed_at":"2022-07-13T13:51:25.112Z","dependency_job_id":null,"html_url":"https://github.com/aktsk/seed_express","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktsk%2Fseed_express","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktsk%2Fseed_express/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktsk%2Fseed_express/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aktsk%2Fseed_express/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aktsk","download_url":"https://codeload.github.com/aktsk/seed_express/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241476391,"owners_count":19968905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-02T07:37:48.682Z","updated_at":"2025-03-02T07:37:50.517Z","avatar_url":"https://github.com/aktsk.png","language":"Ruby","readme":"# seed_express\n\nseed_express は ~~高速に~~ CSV を Database に登録する gem ライブラリです。\u003cbr/\u003e\n以下の特徴があります。\n\n* Rails 3.0 以上対応\n* CSV ファイルの中から、更新された部分のみを検知して、そこだけを DB に反映します。\u003cbr/\u003e\n  大量の更新はそれなりに時間を要しますが、更新が少ない場合は高速かつ安全です。\n* CSV 中の行、列をコメントアウトして、特定のレコード、列の登録を行わないことが可能です。開発中に便利です。\n* 通常の ActiveRecord の Validation が動作します。\n* その他、特殊な Validation をサポートしています。\n\n\n## インストール\n\n次の行をアプリケーションの Gemfile に書き加えてください。\n\n    gem 'seed_express'\n\nそして以下を実行してください。\n\n    $ bundle install\n\nもしくは、自らインストールする場合、以下を実行してください。\n\n    $ gem install seed_express\n\n最後に、以下を実行してください。\n\n    $ bundle exec rake db:migrate\n\n### Rake タスクの作成\n\n末尾に添付のような Rake タスクを作成して、アプリケーションのフォルダにおいてください。\nこの Rake タスクは将来は、 gem に内包する予定です。\n\n\n### 処理対象テーブルの登録\n\n\\#{Rails.root}/db/master_table_list.rb というファイルを以下の様な内容で作成してください。\u003cbr/\u003e\nキーはテーブル名(Ruby シンボル型式)、値はとりあえず空ハッシュを指定してください。\n\n    {\n      :items       =\u003e {},\n      :prefectures =\u003e {},\n      :areas       =\u003e {},\n    }\n\nデータ登録はここで記述した順番に行われます。\nValidation の関係上、親子関係のあるテーブルは、子テーブルから登録をお願いします。\n\n#### テーブルごとの動作のカスタマイズ\n上記の空ハッシュの中で特定のキー(Ruby シンボル)を設定することで、テーブル毎に動作を変更することができます。\n\n##### :nvl_mode\ntrue を設定すると、 CSV 上でカラムに値が設定されていなかった場合にカラムの種類ごとに以下の動作をします。\n\n  * String の場合、 \"\"(長さ 0 の文字列) を設定\n  * Integer の場合、 0 を設定\n\n##### :with_blanks\ntrue を設定すると CSV を読み込んだ際に、カラム名および値の前後の空白を削除してから登録処理を行います。\u003cbr/\u003e\nCSV をスプレッドシートではなく、エディタで編集していた場合、列位置を揃えるために、空白が挿入されている場合がありました。\u003cbr/\u003e\nそのような CSV ファイルを処理できるようにするための機能です。\n\n##### :parent_validation\n特殊な Validation を行うための設定です。\n\n例えば prefectures, cities といったような親子関係があり、\n同じ prefecture 下の city が一つでも更新されたら他の city も validation を行いたい場合があるとします。\nこの時は以下のように設定します。\n\n   :parent_validation =\u003e :prefectures\n\nこの設定により city が更新されると紐づく prefecture も更新されるようになり、\nその結果 prefecture の Validation も実行されるため、\nprefecture 下の cities をまとめての validation が実行されるようになります。\n\n##### :filter_proc\nこの機能は将来廃止または、別の機能への代替を予定しています。\u003cbr/\u003e\nlamba 式を設定すると、テーブルに行が登録される前に呼び出されます。ここで登録する値を変更することが可能です。\n\n引数として登録対象のレコードが Hash 型式で渡されます。\u003cbr/\u003e\nこれを元に処理を行い、 Hash 型式で値を返すと、その返り値の Hash がレコードとして登録されます。\n\n## 使い方\n\n以下のように db:seed_express という Rake タスクを実行してください。\n\n    rake db:seed_express\n\n\n#### オプション\n\n以下のようにオプションの指定が可能です。\n\n    rake db:seed_express TABLES=table1,table2\n\n##### TABLES オプション\n\n    rake db:seed_express TABLES=table1,table2\n\n登録対象のテーブルを指定してます。カンマ区切りで複数の指定が可能です。登録処理はここで記述した順に行われます。\n\n##### TRUNCATE_MODE オプション\n\n    rake db:seed_express TRUNCATE_MODE=true\n\n上記のように設定すると、一旦テーブルの中身を削除して、まっさらな状態からデータ登録を行います。\n\n\n##### FORCE_UPDATE_MODE オプション\n\n    rake db:seed_express FORCE_UPDATE_MODE=true\n\nTRUNCATE_MODE のように中身は削除しませんが、\nテーブル上の全てのレコードを強制的に CSV の内容で更新します。\n\nTRUNCATE_MODE では問題がある場合に使用します。\n例えば一時的にテーブルが空になることで起こる問題を回避することができます。\n\nseed_express はレコード毎に各列がどのような値かを digest 値を求めて記録しておき、次回の登録処理での処理削減に活用しています。\nこの情報が狂うと上手く登録が行われません。これを回避するためのオプションです。\n\n\n## 動作原理\nComming soon...\n\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\n\n### Rake タスク\n\n以下のような rake タスクが必要です。\nいずれ gem に取り込みます。\n\n```ruby\n# -*- coding: utf-8 -*-\n\nnamespace :db do\n  desc \"seed express  (params: TABLES=table1,table2,...  FORCE_UPDATE_MODE=true|TRUNCATE_MODE=true)\"\n  task :seed_express =\u003e :environment do\n    begin\n      target_csv_folder = \"lib/tasks/csv\"\n      STDOUT.sync = true\n\n      error = false\n      filter_tables(master_tables).each_pair do |table_name, options|\n        @showing_table = \"%-36s ... \" % table_name\n        STDOUT.print @showing_table\n        filter_each_lines = if options[:with_blanks]\n                              filter_each_lines = -\u003e(line) { line.chomp.gsub(/ *, */, ',') }\n                            else\n                              nil\n                            end\n\n        options = options.dup\n        options[:filter_each_lines] = filter_each_lines\n        options[:truncate_mode] = true if ENV.has_key?('TRUNCATE_MODE')\n        options[:force_update_mode] = true if ENV.has_key?('FORCE_UPDATE_MODE')\n        options[:datetime_offset] = 9.hours\n\n        inserting_lambda = -\u003e(part_count, part_total, record_count, record_total) { show_info(\"[#{part_count}/#{part_total}] inserting: #{record_count}/#{record_total}\") }\n        upating_lambda = -\u003e(part_count, part_total, record_count, record_total)   { show_info(\"[#{part_count}/#{part_total}] updating: #{record_count}/#{record_total}\") }\n\n        making_bulk_digest_records_lambda = -\u003e(record_count, record_total) { show_info(\"making bulk digest records: #{record_count}/#{record_total}\") }\n        upating_digests_lambda = -\u003e(record_count, record_total)            { show_info(\"updating digests: #{record_count}/#{record_total}\") }\n        inserting_digests_lambda = -\u003e(record_count, record_total)          { show_info(\"inserting digests: #{record_count}/#{record_total}\") }\n\n        options[:callbacks] = {\n          :before_truncating                           =\u003e -\u003e { show_info(\"truncating\") },\n          :before_reading_data                         =\u003e -\u003e { show_info(\"reading\") },\n          :after_reading_data                          =\u003e -\u003e(count) { show_info(\"read: #{count}\") },\n          :before_deleting                             =\u003e -\u003e(count) { show_info(\"deleting: #{count}\") },\n          :after_deleting                              =\u003e -\u003e(count) { show_info(\"deleted: #{count}\") },\n          :before_inserting_a_part                     =\u003e inserting_lambda,\n          :after_inserting_a_part                      =\u003e inserting_lambda,\n          :before_updating_a_part                      =\u003e upating_lambda,\n          :after_updating_a_part                       =\u003e upating_lambda,\n          :before_updating_digests                     =\u003e upating_digests_lambda,\n          :before_updating_a_part_of_digests           =\u003e upating_digests_lambda,\n          :after_updating_a_part_of_digests            =\u003e upating_digests_lambda,\n          :before_making_bulk_digest_records           =\u003e making_bulk_digest_records_lambda,\n          :before_making_a_part_of_bulk_digest_records =\u003e making_bulk_digest_records_lambda,\n          :after_making_a_part_of_bulk_digest_records  =\u003e making_bulk_digest_records_lambda,\n          :before_inserting_digests                    =\u003e inserting_digests_lambda,\n          :before_inserting_a_part_of_digests          =\u003e inserting_digests_lambda,\n          :after_inserting_a_part_of_digests           =\u003e inserting_digests_lambda,\n        }\n\n        seed_express = SeedExpress::CSV.new(table_name, target_csv_folder, options)\n          out = seed_express.import\n          case out[:result]\n          when :skipped\n            show_info(\"doesn't have any changes; skipped(elapsed time: %.2fsec.)\\n\" % out[:elapsed_time])\n          when :error\n            error = true\n            show_info(\"errors have been detected(elapsed time: %.2fsec.)\\n\" % out[:elapsed_time])\n          else\n            show_info(\"inserted: %5d, updated:(prediction: %5d, actual: %5d), deleted: %5d, elapsed time: %.2fsec.\\n\" %\n                      [\n                       out[:inserted_count],\n                       out[:updated_count], out[:actual_updated_count],\n                       out[:deleted_count],\n                       out[:elapsed_time],\n                      ])\n          end\n      end\n      if error\n        raise \"Errors have been detected on any tables\"\n      end\n    end\n  end\nend\n\ndef show_info(msg)\n  reset_line = \"\\x0d\\x1b[K\"\n  STDOUT.print \"#{reset_line}#{@showing_table}#{msg}\"\nend\n\ndef master_tables\n  tables = nil\n  File.open('db/master_table_list.rb') do |f|\n    tables = eval(f.read)\n  end\n\n  tables\nend\n\ndef filter_tables(master_tables)\n  tables = ENV['TABLES'] || ENV['TABLE']\n  return master_tables if tables.blank?\n  tables = tables.split(\",\").map(\u0026:strip)\n\n  hash = {}\n  tables.each do |table|\n    hash[table.to_sym] = master_tables[table.to_sym]\n  end\n  hash\nend\n```\n\n## TODO\n* SeedExpress::CSV や SeedExpress::RbHash の部分にブロックで任意の形式のデータを読み込み可能にする。\n  * わざわざ gem をアップデートしなくても、任意型式に対応したい。\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faktsk%2Fseed_express","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faktsk%2Fseed_express","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faktsk%2Fseed_express/lists"}