diff --git a/.gitignore b/.gitignore index ba1f9bab..521ab3c7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor/ .vscode/ .history/ .env +.envrc node_modules .clasp.* .clasprc.json diff --git a/app/models/character.rb b/app/models/character.rb index 2ae6543d..103fc508 100644 --- a/app/models/character.rb +++ b/app/models/character.rb @@ -4,4 +4,27 @@ class Character < ApplicationRecord has_many :character_nicknames, dependent: :destroy has_many :nicknames, through: :character_nicknames + + def product_names_for_result_tweet + # TODO: 共通の値としてどこかに置きたい + name_convert_list = { + '幻想水滸伝' => 'I', + '幻想水滸伝II' => 'II', + '幻想水滸外伝Vol.1' => '外伝1', + '幻想水滸外伝Vol.2' => '外伝2', + '幻想水滸伝III' => 'III', + '幻想水滸伝IV' => 'IV', + 'Rhapsodia' => 'R', + '幻想水滸伝V' => 'V', + '幻想水滸伝ティアクライス' => 'TK', + '幻想水滸伝 紡がれし百年の時' => '紡時' + } + + converted_product_names = products.map do |product| + name_convert_list[product.name] + end + + # Character.first.product_names_for_result_tweet #=> "(I,II,外伝2)" + "(#{converted_product_names.join(',')})" + end end diff --git a/app/models/concerns/counting_tools.rb b/app/models/concerns/counting_tools.rb new file mode 100644 index 00000000..9c2ec85b --- /dev/null +++ b/app/models/concerns/counting_tools.rb @@ -0,0 +1,30 @@ +module CountingTools + extend ActiveSupport::Concern + + included do + scope :other_tweets_exists, -> { where.not(other_tweet_ids_text: '') } + end + + module ClassMethods + def foobar; end + end + + def three_chara_names + [chara_1, chara_2, chara_3] + end + + def convert_chara_names_to_array_in_character_and_products_hashes(chara_names) + result_array = [] + + chara_names.each do |chara_name| + inserted_hash = {} + + inserted_hash['character'] = Character.find_by(name: chara_name) + inserted_hash['products'] = Character.find_by(name: chara_name).products + + result_array << inserted_hash + end + + result_array + end +end diff --git a/app/models/counting_all_character.rb b/app/models/counting_all_character.rb new file mode 100644 index 00000000..c2f8f5e4 --- /dev/null +++ b/app/models/counting_all_character.rb @@ -0,0 +1,77 @@ +class CountingAllCharacter < ApplicationRecord + include CountingTools + + belongs_to :tweet, optional: true + belongs_to :direct_message, optional: true + belongs_to :user # 鍵でも User のレコードは取得できるので optional 指定は不要 + + scope :invisible, -> { where(is_invisible: true) } + scope :out_of_counting, -> { where(is_out_of_counting: true) } + scope :valid_records, -> { where(is_out_of_counting: false).where(is_invisible: false) } + + enum vote_method: { by_tweet: 0, by_direct_message: 1, by_others: 99 } + + def self.tweets_whose_invisible_status_is_different_between_sheet_and_database + sheet_invisible_tweet_ids = CountingAllCharacter.invisible.pluck(:tweet_id) + + result = [] + sheet_invisible_tweet_ids.each do |tweet_id| + database_tweet = Tweet.find_by(id: tweet_id) + + result << CountingAllCharacter.find_by(tweet_id: tweet_id) if CountingAllCharacter.find_by(tweet_id: tweet_id).is_invisible == database_tweet.is_public + end + + result + end + + # キャラ1〜3は、AIがsuggestしたものに含まれるか否か? + # キャラ名はキャラデータベースにあるものと一致しているか? + # 同一人物の複数ツイートで複数計上していないか + def self.tweeted_all_characters + chara_1_column_characters = CountingAllCharacter.pluck(:chara_1) + chara_2_column_characters = CountingAllCharacter.pluck(:chara_2) + chara_3_column_characters = CountingAllCharacter.pluck(:chara_3) + + (chara_1_column_characters + chara_2_column_characters + chara_3_column_characters).uniq.compact.sort + end + + def self.character_db_diff + result = [] + + tweeted_all_characters.each do |character| + result << character if Character.where(name: character).blank? + end + + result + end + + # もとの行の内容が知りたい + # どうだったらOKでどうだったらNGなのか? + # キャラ計上数が4以上だとNG + # other_tweetがvalid_recordsでないならスルー + def self.check_other_tweets + result = [] + + CountingAllCharacter.valid_records.other_tweets_exists.each do |c| + c.other_tweets.each do |t| + # tweet = Tweet.find_by(id: t.tweet_id) + + # result << tweet if tweet.is_public? + + result << t if !t.is_out_of_counting && !t.is_invisible? + end + end + + result + end + + def other_tweets + # CountingAllCharacter.where('other_tweet_ids_text like ?', '%|%') + CountingAllCharacter.includes(:tweet).where( + tweet: + { + id_number: other_tweet_ids_text.split(',') + } + ) + end +end diff --git a/app/models/counting_unite_attack.rb b/app/models/counting_unite_attack.rb new file mode 100644 index 00000000..b2ca99f3 --- /dev/null +++ b/app/models/counting_unite_attack.rb @@ -0,0 +1,13 @@ +class CountingUniteAttack < ApplicationRecord + include CountingTools + + belongs_to :tweet, optional: true + belongs_to :direct_message, optional: true + belongs_to :user # 鍵でも User のレコードは取得できるので optional 指定は不要 + + scope :invisible, -> { where(is_invisible: true) } + scope :out_of_counting, -> { where(is_out_of_counting: true) } + scope :valid_records, -> { where(is_out_of_counting: false).where(is_invisible: false) } + + enum vote_method: { by_tweet: 0, by_direct_message: 1, by_others: 99 } +end diff --git a/app/models/direct_message.rb b/app/models/direct_message.rb index d7df08be..62b66f35 100644 --- a/app/models/direct_message.rb +++ b/app/models/direct_message.rb @@ -13,6 +13,14 @@ def self.for_spreadsheet .order(id_number: :asc) end + def self.missing_records + where(id_number: 1540436647376031755..1540790299618066435) + end + + def is_missing_record? + id_number.in?(1540436647376031755..1540790299618066435) + end + # self.user と同義 def sender User.find_by(id_number: sender_id_number) diff --git a/app/services/dm/importer.rb b/app/services/dm/importer.rb index c16ee76d..ed577d9d 100644 --- a/app/services/dm/importer.rb +++ b/app/services/dm/importer.rb @@ -9,18 +9,20 @@ class Importer INTERVAL_SECONDS = 10 # 最新のものから過去のものに向かって取得していき、取得できなくなったら抜ける - def self.exec(twitter_client: nil, number_of_getters: 1) + def self.exec(twitter_client: nil, number_of_getters: 1, get_dm_per_request: 50) twitter_client = TwitterRestApi.client(account_key: :gensosenkyo) if twitter_client.blank? next_cursor = nil loop_counter = 0 while true - # count は 1 にしておくのが望ましい(それでも28とか取ってきちゃうので) + # count は ループ段階に入ったら 1 にしておくのが望ましい(それでも28とか取ってきちゃうので) if next_cursor.nil? - dm_events = twitter_client.direct_messages_events(count: 1) + # ここで API リクエスト が発生する + dm_events = twitter_client.direct_messages_events(count: get_dm_per_request) else - dm_events = twitter_client.direct_messages_events(count: 1, cursor: next_cursor) + # ここで API リクエスト が発生する + dm_events = twitter_client.direct_messages_events(count: get_dm_per_request, cursor: next_cursor) end dm_events = dm_events.to_h @@ -33,8 +35,6 @@ def self.exec(twitter_client: nil, number_of_getters: 1) create_user_record(dm, twitter_client) create_direct_message_record(dm, event) - - sleep INTERVAL_SECONDS end end @@ -43,7 +43,8 @@ def self.exec(twitter_client: nil, number_of_getters: 1) break if next_cursor.nil? break if loop_counter >= number_of_getters - sleep INTERVAL_SECONDS * 5 + # ループの最初に API リクエスト が発生したので待つ + sleep INTERVAL_SECONDS * 3 end "[DONE] Dm::Importer.exec" @@ -57,7 +58,7 @@ def self.create_user_record(dm, twitter_client) existing_user = User.find_by(id_number: user_id_number) if existing_user.blank? - # Twitter API によりユーザー情報を取得する + # Twitter API によりユーザー情報を取得する(API リクエスト が発生する) target_user = twitter_client.user(user_id_number) user = User.new( @@ -70,6 +71,9 @@ def self.create_user_record(dm, twitter_client) ) user.save! + + # API リクエスト が発生したので待つ + sleep INTERVAL_SECONDS end end diff --git a/app/services/sheets/counting/all_characters.rb b/app/services/sheets/counting/all_characters.rb new file mode 100644 index 00000000..801a634d --- /dev/null +++ b/app/services/sheets/counting/all_characters.rb @@ -0,0 +1,140 @@ +module Sheets + module Counting + class AllCharacters + def self.import_via_tweet + sheet_names = YAML.load_file(Rails.root.join('config/counting_sheet_names.yml'))['names'] + + ActiveRecord::Base.transaction do + sheet_names.each_with_index do |sheet_name, i| + rows = SheetData.get_rows(sheet_id: ENV.fetch('COUNTING_ALL_CHARACTERS_SHEET_ID', nil), range: "#{sheet_names[i]}!A2:Q101") + + rows.each do |row| + # TODO: 設定ファイルを用いてよりスマートに定義したい + column_vs_value = { + id_on_sheet: row[0], + tweet_id_number: row[2], + other_tweet_ids_text: row[5], + is_invisible: row[7], # "FALSE" のような文字列なので注意 + is_out_of_counting: row[8], # "FALSE" のような文字列なので注意 + contents: row[11], + memo: row[12], + chara_1: row[14], + chara_2: row[15], + chara_3: row[16] + } + + next if column_vs_value[:id_on_sheet].blank? || column_vs_value[:tweet_id_number].blank? || column_vs_value[:contents].blank? + + tweet = Tweet.find_by(id_number: column_vs_value[:tweet_id_number]) + tweet_id = tweet.id + user_id = tweet.user.id + + unique_attrs = { + id_on_sheet: column_vs_value[:id_on_sheet], + user_id: user_id, + vote_method: :by_tweet, + tweet_id: tweet_id, + contents: column_vs_value[:contents], + memo: column_vs_value[:memo] + } + + mutable_attrs = { + # 123456,654321,777777 のような文字列になる + other_tweet_ids_text: column_vs_value[:other_tweet_ids_text].split('|').map(&:strip).join(','), + is_invisible: column_vs_value[:is_invisible].to_boolean, + is_out_of_counting: column_vs_value[:is_out_of_counting].to_boolean, + chara_1: column_vs_value[:chara_1], + chara_2: column_vs_value[:chara_2], + chara_3: column_vs_value[:chara_3] + } + + CountingAllCharacter.find_or_initialize_by(unique_attrs).update!(mutable_attrs) + end + end + end + + '[DONE] Sheets::Counting::AllCharacters.import_via_tweet' + end + + def self.import_via_dm + sheet_names = YAML.load_file(Rails.root.join('config/counting_sheet_names.yml'))['names'] + + # rubocop:disable Metrics/BlockLength + ActiveRecord::Base.transaction do + sheet_names.each_with_index do |sheet_name, i| + rows = SheetData.get_rows(sheet_id: ENV.fetch('COUNTING_DIRECT_MESSAGES_SHEET_ID', nil), range: "#{sheet_names[i]}!A2:Q101") + + rows.each do |row| + # TODO: 設定ファイルを用いてスマートに定義したい + column_vs_value = { + id_on_sheet: row[0], + dm_id_number: row[2], + is_invisible: row[5], # "FALSE" のような文字列なので注意 + is_out_of_counting: row[6], # "FALSE" のような文字列なので注意 + category: row[9], + contents: row[10], + memo: row[11], + input_01: row[13], + input_02: row[14], + input_03: row[15], + input_04: row[16], + input_05: row[17], + input_06: row[18], + input_07: row[19], + input_08: row[20], + input_09: row[21], + input_10: row[22] + } + + # DMの書式が自由すぎるので、こちらで条件を吸収する + next if column_vs_value[:category] != '①オールキャラ部門' && column_vs_value[:category] != '両部門' + + next if column_vs_value[:id_on_sheet].blank? || column_vs_value[:dm_id_number].blank? || column_vs_value[:contents].blank? + + dm = DirectMessage.find_by(id_number: column_vs_value[:dm_id_number]) + dm_id = dm.id + user_id = dm.user.id + + unique_attrs = { + id_on_sheet: column_vs_value[:id_on_sheet], + user_id: user_id, + vote_method: :by_direct_message, + direct_message_id: dm_id, + other_tweet_ids_text: nil, + contents: column_vs_value[:contents], + memo: column_vs_value[:memo] + } + + # 「両部門の場合は、N列とO列に協力攻撃、P列Q列R列にオールキャラ部門を入力する」という例外規定 + case column_vs_value[:category] + when '①オールキャラ部門' + chara_1 = column_vs_value[:input_01] + chara_2 = column_vs_value[:input_02] + chara_3 = column_vs_value[:input_03] + when '両部門' + chara_1 = column_vs_value[:input_03] + chara_2 = column_vs_value[:input_04] + chara_3 = column_vs_value[:input_05] + end + + mutable_attrs = { + is_invisible: column_vs_value[:is_invisible].to_boolean, + is_out_of_counting: column_vs_value[:is_out_of_counting].to_boolean, + chara_1: chara_1, + chara_2: chara_2, + chara_3: chara_3 + } + + CountingAllCharacter.find_or_initialize_by(unique_attrs).update!(mutable_attrs) + end + + puts "#{sheet_names[i]} is Done." # rubocop:disable Rails/Output + end + end + # rubocop:enable Metrics/BlockLength + + '[DONE] Sheets::Counting::AllCharacters.import_via_dm' + end + end + end +end diff --git a/app/services/sheets/counting/unite_attacks.rb b/app/services/sheets/counting/unite_attacks.rb new file mode 100644 index 00000000..f8d00113 --- /dev/null +++ b/app/services/sheets/counting/unite_attacks.rb @@ -0,0 +1,123 @@ +module Sheets + module Counting + class UniteAttacks + def self.import_via_tweet + sheet_names = YAML.load_file(Rails.root.join('config/counting_sheet_names.yml'))['names'] + + ActiveRecord::Base.transaction do + sheet_names.each_with_index do |sheet_name, i| + rows = SheetData.get_rows(sheet_id: ENV.fetch('COUNTING_UNITE_ATTACKS_SHEET_ID', nil), range: "#{sheet_names[i]}!A2:Q101") + + rows.each do |row| + # TODO: 設定ファイルを用いてスマートに定義したい + column_vs_value = { + id_on_sheet: row[0], + tweet_id_number: row[2], + other_tweet_ids_text: row[5], + is_invisible: row[7], # "FALSE" のような文字列なので注意 + is_out_of_counting: row[8], # "FALSE" のような文字列なので注意 + contents: row[11], + memo: row[12], + product_name: row[14], + unite_attack_name: row[15] + } + + next if column_vs_value[:id_on_sheet].blank? || column_vs_value[:tweet_id_number].blank? || column_vs_value[:contents].blank? + + tweet = Tweet.find_by(id_number: column_vs_value[:tweet_id_number]) + tweet_id = tweet.id + user_id = tweet.user.id + + unique_attrs = { + id_on_sheet: column_vs_value[:id_on_sheet], + user_id: user_id, + vote_method: :by_tweet, + tweet_id: tweet_id, + contents: column_vs_value[:contents], + memo: column_vs_value[:memo] + } + + mutable_attrs = { + # 123456,654321,777777 のような文字列になる + other_tweet_ids_text: column_vs_value[:other_tweet_ids_text].split('|').map(&:strip).join(','), + is_invisible: column_vs_value[:is_invisible].to_boolean, + is_out_of_counting: column_vs_value[:is_out_of_counting].to_boolean, + product_name: column_vs_value[:product_name], + unite_attack_name: column_vs_value[:unite_attack_name] + } + + CountingUniteAttack.find_or_initialize_by(unique_attrs).update!(mutable_attrs) + end + end + end + + '[DONE] Sheets::Counting::UniteAttacks.import_via_tweet' + end + + def self.import_via_dm + sheet_names = YAML.load_file(Rails.root.join('config/counting_sheet_names.yml'))['names'] + + ActiveRecord::Base.transaction do + sheet_names.each_with_index do |sheet_name, i| + rows = SheetData.get_rows(sheet_id: ENV.fetch('COUNTING_DIRECT_MESSAGES_SHEET_ID', nil), range: "#{sheet_names[i]}!A2:Q101") + + rows.each do |row| + # TODO: 設定ファイルを用いてスマートに定義したい + column_vs_value = { + id_on_sheet: row[0], + dm_id_number: row[2], + is_invisible: row[5], # "FALSE" のような文字列なので注意 + is_out_of_counting: row[6], # "FALSE" のような文字列なので注意 + category: row[9], + contents: row[10], + memo: row[11], + input_01: row[13], + input_02: row[14], + input_03: row[15], + input_04: row[16], + input_05: row[17], + input_06: row[18], + input_07: row[19], + input_08: row[20], + input_09: row[21], + input_10: row[22] + } + + next if column_vs_value[:category] != '②協力攻撃部門' || column_vs_value[:category] != '両部門' + + next if column_vs_value[:id_on_sheet].blank? || column_vs_value[:dm_id_number].blank? || column_vs_value[:contents].blank? + + dm = DirectMessage.find_by(id_number: column_vs_value[:dm_id_number]) + dm_id = dm.id + user_id = dm.user.id + + unique_attrs = { + id_on_sheet: column_vs_value[:id_on_sheet], + user_id: user_id, + vote_method: :by_direct_message, + direct_message_id: dm_id, + other_tweet_ids_text: nil, + contents: column_vs_value[:contents], + memo: column_vs_value[:memo] + } + + # 「両部門の場合は、N列とO列に協力攻撃、P列Q列R列にオールキャラ部門を入力する」という例外規定 + mutable_attrs = { + is_invisible: column_vs_value[:is_invisible].to_boolean, + is_out_of_counting: column_vs_value[:is_out_of_counting].to_boolean, + product_name: column_vs_value[:input_01], + unite_attack_name: column_vs_value[:input_02] + } + + CountingUniteAttack.find_or_initialize_by(unique_attrs).update!(mutable_attrs) + end + + puts "#{sheet_names[i]} is Done." # rubocop:disable Rails/Output + end + end + + '[DONE] Sheets::Counting::UniteAttacks.import_via_dm' + end + end + end +end diff --git a/app/services/sheets/write_and_update/all_characters.rb b/app/services/sheets/write_and_update/all_characters.rb index d4b0c289..f72e2a18 100644 --- a/app/services/sheets/write_and_update/all_characters.rb +++ b/app/services/sheets/write_and_update/all_characters.rb @@ -18,6 +18,8 @@ def self.exec by_user_other_tweets_for_sheet = by_user_other_tweets.map { |t| t.id_number.to_s }.join(' | ').to_s inserted_hash['screen_name'] = tweet.user.screen_name + # to_s しても、たとえば 1540956021363200000 のような値はダメ + # セル側の書式を「書式なしテキスト」にする必要がある inserted_hash['tweet_id'] = tweet.id_number.to_s inserted_hash['日時'] = tweet.tweeted_at.strftime('%Y/%m/%d %H:%M:%S').to_s inserted_hash['URL'] = tweet.url diff --git a/app/services/sheets/write_and_update/direct_messages.rb b/app/services/sheets/write_and_update/direct_messages.rb index eccc4f29..9337607f 100644 --- a/app/services/sheets/write_and_update/direct_messages.rb +++ b/app/services/sheets/write_and_update/direct_messages.rb @@ -1,13 +1,20 @@ module Sheets module WriteAndUpdate class DirectMessages - def self.exec - direct_messages = DirectMessage.for_spreadsheet + # データベースサーバが停止していたため、落ちていた際のレコードは別扱いにする + def self.exec(complement_missing_messages: false) + direct_messages = if complement_missing_messages + DirectMessage.missing_records.for_spreadsheet + else + DirectMessage.for_spreadsheet + end direct_messages.each_slice(100).with_index do |dm_100, index_on_hundred| prepared_written_data_by_array_in_hash = [] dm_100.each_with_index do |dm, i| + next if complement_missing_messages == false && dm.is_missing_record? + inserted_hash = {} inserted_hash['screen_name'] = dm.user.screen_name @@ -20,16 +27,31 @@ def self.exec prepared_written_data_by_array_in_hash << inserted_hash end - two_digit_number = format('%02d', number: index_on_hundred + 1) - sheet_name = "集計_#{two_digit_number}" + two_digit_number = if complement_missing_messages + # 取得漏れは '集計_15' から記録する + format( + '%02d', + number: (index_on_hundred + 14) + 1 + ) + else + format( + '%02d', + number: index_on_hundred + 1 + ) + end + sheet_name = "集計_#{two_digit_number}" written_data = [] prepared_written_data_by_array_in_hash.each_with_index do |written_data_hash, index| row = [] # TODO: 取得漏れには 10001 始まりを付与したい - id_on_sheet = (index_on_hundred * 100) + (index + 1) + id_on_sheet = if complement_missing_messages + ((index_on_hundred + 14) * 100) + (index + 1) + else + (index_on_hundred * 100) + (index + 1) + end # TODO: ハードコーディングをしたくない row[0] = id_on_sheet diff --git a/config/counting_sheet_names.yml b/config/counting_sheet_names.yml index 20273d6f..d0d1665c 100644 --- a/config/counting_sheet_names.yml +++ b/config/counting_sheet_names.yml @@ -1,10 +1,22 @@ names: - - 投票まとめ - - ①オールキャラ部門 - - ②協力攻撃部門 - - DM(ダイレクトメッセージ) - - ボーナス票・OP・CLイラスト - - ボーナス票・お題小説 - - ボーナス票・開票イラスト - - ボーナス票・推し台詞 - - ボーナス票・選挙運動 + - 集計_01 + - 集計_02 + - 集計_03 + - 集計_04 + - 集計_05 + - 集計_06 + - 集計_07 + - 集計_08 + - 集計_09 + - 集計_10 + - 集計_11 + - 集計_12 + - 集計_13 + - 集計_14 + - 集計_15 + - 集計_16 + - 集計_17 + - 集計_18 + - 集計_19 + - 集計_20 + - 取得漏れ等 diff --git a/config/counting_spreadsheet_names.yml b/config/counting_spreadsheet_names.yml new file mode 100644 index 00000000..20273d6f --- /dev/null +++ b/config/counting_spreadsheet_names.yml @@ -0,0 +1,10 @@ +names: + - 投票まとめ + - ①オールキャラ部門 + - ②協力攻撃部門 + - DM(ダイレクトメッセージ) + - ボーナス票・OP・CLイラスト + - ボーナス票・お題小説 + - ボーナス票・開票イラスト + - ボーナス票・推し台詞 + - ボーナス票・選挙運動 diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index d472060b..03bbdce0 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -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--tWboWzwHyTIMYXWy--LuCQ4TwI3vXOXnVBvBo04w== \ No newline at end of file +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--UuNC9JJiNNikFdy2--RSpl5trbJFcoVjqg/7F7JA== \ No newline at end of file diff --git a/config/initializers/string.rb b/config/initializers/string.rb new file mode 100644 index 00000000..45520000 --- /dev/null +++ b/config/initializers/string.rb @@ -0,0 +1,8 @@ +class String + def to_boolean + return true if self == 'TRUE' + return false if self == 'FALSE' + + nil + end +end diff --git a/config/sheet_headers_and_column_names_relations/counting_all_characters.yml b/config/sheet_headers_and_column_names_relations/counting_all_characters.yml index 565613f0..a0586087 100644 --- a/config/sheet_headers_and_column_names_relations/counting_all_characters.yml +++ b/config/sheet_headers_and_column_names_relations/counting_all_characters.yml @@ -1,50 +1,26 @@ -- # UNIQUE +- sheet_header: ID column_name: id_on_sheet - - sheet_header: screen_name - column_name: screen_name -- - sheet_header: tweet_id # name - column_name: tweet_id_number -- - sheet_header: 日時 - column_name: tweeted_at -- - sheet_header: URL - column_name: url -- - sheet_header: 別ツイ # カンマ区切りテキスト - column_name: other_tweets -- - sheet_header: 全終了? - column_name: is_all_completed + sheet_header: 別ツイ + column_name: other_tweet_ids_text - sheet_header: ツイ見られない? - column_name: is_tweet_invisible + column_name: is_invisible - sheet_header: 集計対象外? - column_name: is_not_included_counting -- - sheet_header: ふぁぼ済? - column_name: is_already_favorite -- - sheet_header: 二次チェック済? - column_name: is_already_second_check + column_name: is_out_of_counting - sheet_header: 内容 - column_name: full_text + column_name: contents - sheet_header: 備考 column_name: memo - - sheet_header: 要レビュー? - column_name: is_review_required -- - sheet_header: キャラ1 # キャラ1 or 作品名 + sheet_header: キャラ1 column_name: character_01 - - sheet_header: キャラ2 # キャラ2 or 協力攻撃名 + sheet_header: キャラ2 column_name: character_02 - sheet_header: キャラ3 diff --git a/db/migrate/20220702100040_create_counting_all_characters.rb b/db/migrate/20220702100040_create_counting_all_characters.rb new file mode 100644 index 00000000..785c7039 --- /dev/null +++ b/db/migrate/20220702100040_create_counting_all_characters.rb @@ -0,0 +1,21 @@ +class CreateCountingAllCharacters < ActiveRecord::Migration[7.0] + def change + create_table :counting_all_characters do |t| + t.integer :id_on_sheet # tweet の id と dm の id とで重複があり得る + t.integer :user_id, null: false + t.integer :vote_method, null: false + t.integer :tweet_id # dm の場合は NULL があり得る + t.integer :direct_message_id + t.string :other_tweet_ids_text # "|" で区切った文字列 + t.boolean :is_invisible + t.boolean :is_out_of_counting + t.string :contents + t.string :memo + t.string :chara_1 + t.string :chara_2 + t.string :chara_3 + + t.timestamps + end + end +end diff --git a/db/migrate/20220703055608_create_counting_unite_attacks.rb b/db/migrate/20220703055608_create_counting_unite_attacks.rb new file mode 100644 index 00000000..4916321a --- /dev/null +++ b/db/migrate/20220703055608_create_counting_unite_attacks.rb @@ -0,0 +1,20 @@ +class CreateCountingUniteAttacks < ActiveRecord::Migration[7.0] + def change + create_table :counting_unite_attacks do |t| + t.integer :id_on_sheet # tweet の id と dm の id とで重複があり得る + t.integer :user_id, null: false + t.integer :vote_method, null: false + t.integer :tweet_id # dm の場合は NULL があり得る + t.integer :direct_message_id + t.string :other_tweet_ids_text # "|" で区切った文字列 + t.boolean :is_invisible + t.boolean :is_out_of_counting + t.string :contents + t.string :memo + t.string :product_name + t.string :unite_attack_name + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a6dfaff5..86f47a97 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2022_06_28_131500) do +ActiveRecord::Schema[7.0].define(version: 2022_07_03_055608) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -62,6 +62,41 @@ t.datetime "updated_at", null: false end + create_table "counting_all_characters", force: :cascade do |t| + t.integer "id_on_sheet" + t.integer "user_id", null: false + t.integer "vote_method", null: false + t.integer "tweet_id" + t.integer "direct_message_id" + t.string "other_tweet_ids_text" + t.boolean "is_invisible" + t.boolean "is_out_of_counting" + t.string "contents" + t.string "memo" + t.string "chara_1" + t.string "chara_2" + t.string "chara_3" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "counting_unite_attacks", force: :cascade do |t| + t.integer "id_on_sheet" + t.integer "user_id", null: false + t.integer "vote_method", null: false + t.integer "tweet_id" + t.integer "direct_message_id" + t.string "other_tweet_ids_text" + t.boolean "is_invisible" + t.boolean "is_out_of_counting" + t.string "contents" + t.string "memo" + t.string "product_name" + t.string "unite_attack_name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "direct_messages", force: :cascade do |t| t.bigint "id_number" t.datetime "messaged_at", precision: nil diff --git a/lib/tasks/change_is_public_attributes.rake b/lib/tasks/change_is_public_attributes.rake index 675aef65..7557d4e7 100644 --- a/lib/tasks/change_is_public_attributes.rake +++ b/lib/tasks/change_is_public_attributes.rake @@ -1,6 +1,7 @@ namespace :change_is_public_attributes do desc 'ツイートが非公開または削除になっていないかを調べ、変更があれば UPDATE を実行する' task all: :environment do + # NOTE: この tweets の取り方をしているのでツイートが増えることによる実行タイムアウトに注意する tweets = Tweet.all tweet_id_numbers = tweets.map(&:id_number) diff --git a/lib/tasks/import_counting_all_characters.rake b/lib/tasks/import_counting_all_characters.rake new file mode 100644 index 00000000..d778eb3b --- /dev/null +++ b/lib/tasks/import_counting_all_characters.rake @@ -0,0 +1,11 @@ +namespace :import_counting_all_characters do + desc '「オールキャラ部門」のツイート投票の開票データをスプレッドシートからインポートする' + task exec_via_tweet: :environment do + Sheets::Counting::AllCharacters.import_via_tweet + end + + desc '「オールキャラ部門」のDM投票の開票データをスプレッドシートからインポートする' + task exec_via_dm: :environment do + # Sheets::Counting::AllCharacters.import_via_dm + end +end diff --git a/lib/tasks/import_counting_unite_attacks.rake b/lib/tasks/import_counting_unite_attacks.rake new file mode 100644 index 00000000..0191d788 --- /dev/null +++ b/lib/tasks/import_counting_unite_attacks.rake @@ -0,0 +1,11 @@ +namespace :import_counting_unite_attacks do + desc '「協力攻撃部門」のツイート投票の開票データをスプレッドシートからインポートする' + task exec_via_tweet: :environment do + Sheets::Counting::UniteAttacks.import_via_tweet + end + + desc '「協力攻撃部門」のDM投票の開票データをスプレッドシートからインポートする' + task exec_via_dm: :environment do + Sheets::Counting::UniteAttacks.import_via_dm + end +end