From f5d82e1071875ea73cedd9d3dcee5ab5892a27fe Mon Sep 17 00:00:00 2001 From: Sean Eshbaugh Date: Mon, 31 Mar 2014 18:50:45 -0500 Subject: [PATCH] Added import tasks. --- Gemfile | 1 + Gemfile.lock | 21 ++ app/models/card.rb | 8 +- app/models/card_part.rb | 34 ++++ lib/tasks/import.rake | 437 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 app/models/card_part.rb diff --git a/Gemfile b/Gemfile index 140ae4d..1b7603f 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,7 @@ gem 'devise' gem 'exception_notification' gem 'honeypot-captcha' gem 'kaminari' +gem 'mechanize' gem 'ransack' gem 'rmagick' gem 'sanitize', '2.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 90367be..842a677 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,8 @@ GEM railties (~> 3.1) warden (~> 1.2.1) diff-lcs (1.2.4) + domain_name (0.5.18) + unf (>= 0.0.5, < 1.0.0) erubis (2.7.0) eventmachine (1.0.3) exception_notification (4.0.0) @@ -89,6 +91,8 @@ GEM highline (1.6.19) hike (1.2.3) honeypot-captcha (0.0.2) + http-cookie (1.0.2) + domain_name (~> 0.5) i18n (0.6.1) journey (1.0.4) jquery-rails (3.0.4) @@ -119,12 +123,23 @@ GEM skinny (~> 0.2.3) sqlite3 (~> 1.3) thin (~> 1.5.0) + mechanize (2.7.2) + domain_name (~> 0.5, >= 0.5.1) + http-cookie (~> 1.0.0) + mime-types (~> 1.17, >= 1.17.2) + net-http-digest_auth (~> 1.1, >= 1.1.1) + net-http-persistent (~> 2.5, >= 2.5.2) + nokogiri (~> 1.4) + ntlm-http (~> 0.1, >= 0.1.1) + webrobots (>= 0.0.9, < 0.2) metaclass (0.0.1) mime-types (1.23) mocha (0.14.0) metaclass (~> 0.0.1) multi_json (1.7.7) mysql2 (0.3.11) + net-http-digest_auth (1.4) + net-http-persistent (2.9.4) net-scp (1.1.2) net-ssh (>= 2.6.5) net-sftp (2.1.2) @@ -133,6 +148,7 @@ GEM net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) nokogiri (1.5.10) + ntlm-http (0.1.1) orm_adapter (0.4.0) polyamorous (0.5.0) activerecord (~> 3.0) @@ -235,8 +251,12 @@ GEM uglifier (2.1.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) + unf (0.1.3) + unf_ext + unf_ext (0.0.6) warden (1.2.2) rack (>= 1.0) + webrobots (0.1.1) websocket (1.0.7) xpath (1.0.0) nokogiri (~> 1.3) @@ -266,6 +286,7 @@ DEPENDENCIES less-rails libv8 (= 3.11.8.13) mailcatcher + mechanize mysql2 quiet_assets rails (= 3.2.13) diff --git a/app/models/card.rb b/app/models/card.rb index 2df759d..d968a56 100644 --- a/app/models/card.rb +++ b/app/models/card.rb @@ -1,8 +1,9 @@ class Card < ActiveRecord::Base - attr_accessible :multiverse_id, :name, :card_set_id, :mana_cost, :converted_mana_cost, :card_type, :card_text, :flavor_text, :power, :toughness, :loyalty, :rarity, :card_number, :artist + attr_accessible :multiverse_id, :name, :card_set_id, :layout, :mana_cost, :converted_mana_cost, :colors, :card_type, :card_supertypes, :card_types, :card_subtypes, :card_text, :flavor_text, :power, :toughness, :loyalty, :rarity, :card_number, :artist belongs_to :card_set + has_many :card_parts has_many :collections has_many :users, :through => :collections @@ -16,9 +17,14 @@ class Card < ActiveRecord::Base if self.new_record? self.multiverse_id ||= '' self.name ||= '' + self.layout ||= '' self.mana_cost ||= '' self.converted_mana_cost ||= '' + self.colors ||= '' self.card_type ||= '' + self.card_supertypes ||= '' + self.card_types ||= '' + self.card_subtypes ||= '' self.card_text ||= '' self.flavor_text ||= '' self.power ||= '' diff --git a/app/models/card_part.rb b/app/models/card_part.rb new file mode 100644 index 0000000..a3f44ab --- /dev/null +++ b/app/models/card_part.rb @@ -0,0 +1,34 @@ +class CardPart < ActiveRecord::Base + attr_accessible :multiverse_id, :name, :card_id, :layout, :mana_cost, :converted_mana_cost, :colors, :card_type, :card_supertypes, :card_types, :card_subtypes, :card_text, :flavor_text, :power, :toughness, :loyalty, :rarity, :card_number, :artist + + belongs_to :card + + validates_presence_of :multiverse_id + + validates_presence_of :name + + validates_presence_of :card_id + + after_initialize do + if self.new_record? + self.multiverse_id ||= '' + self.name ||= '' + self.layout ||= '' + self.mana_cost ||= '' + self.converted_mana_cost ||= '' + self.colors ||= '' + self.card_type ||= '' + self.card_supertypes ||= '' + self.card_types ||= '' + self.card_subtypes ||= '' + self.card_text ||= '' + self.flavor_text ||= '' + self.power ||= '' + self.toughness ||= '' + self.loyalty ||= '' + self.rarity ||= '' + self.card_number ||= '' + self.artist ||= '' + end + end +end diff --git a/lib/tasks/import.rake b/lib/tasks/import.rake index c736ab9..f2fc05e 100644 --- a/lib/tasks/import.rake +++ b/lib/tasks/import.rake @@ -242,4 +242,441 @@ namespace :import do end end end + + desc 'Import card data.' + task :cards, [:set] => [:environment] do |_, args| + set_codes = ([args[:set]].compact + args.extras).map { |set_code| set_code.upcase } + + set_codes.each do |set_code| + cached_file_path = Rails.root.join('tmp', 'set_data', "#{set_code}.json") + + unless File.exist?(cached_file_path) + agent = Mechanize.new + + response = agent.get("http://mtgjson.com/json/#{set_code}.json") + + if response.code == '200' + FileUtils.mkdir_p(File.dirname(cached_file_path)) + + cached_file = File.new(cached_file_path, 'w') + + cached_file.puts response.body + + cached_file.close + else + puts raise "Could not retrieve http://mtgjson.com/json/#{set_code}.json. Got error code #{response.code}." + end + end + + imported_cards = JSON.parse(File.read(cached_file_path))['cards'] + + card_set = CardSet.where(:code => set_code).includes(:cards).first + + raise "Card set with code #{set_code} not found." unless card_set.present? + + puts "Importing cards for #{card_set.name}(#{card_set.code})." + + cards = card_set.cards + + if imported_cards.length != cards.length + puts "Warning: #{card_set.name} cards length does not match imported length." + end + + def create_or_update_card(imported_card, card, card_set) + colors = (imported_card['colors'] || []).join(';') + + card_supertypes = (imported_card['supertypes'] || []).join(';') + + card_types = (imported_card['types'] || []).join(';') + + card_subtypes = (imported_card['subtypes'] || []).join(';') + + if card.present? + if card.update_attributes({ + :name => imported_card['name'].to_s, + :layout => imported_card['layout'].to_s, + :mana_cost => imported_card['manaCost'].to_s, + :converted_mana_cost => imported_card['cmc'].to_s, + :colors => colors, + :card_type => imported_card['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card['flavor'].to_s, + :power => imported_card['power'].to_s, + :toughness => imported_card['toughness'].to_s, + :loyalty => imported_card['loyalty'].to_s, + :rarity => imported_card['rarity'].to_s, + :card_number => imported_card['number'].to_s, + :artist => imported_card['artist'].to_s + }) + puts "Updated existing card #{card.id} #{card.name}(#{card.multiverse_id})." + else + puts "Failed to update existing card #{card.name}(#{card.multiverse_id}). #{card.errors.full_messages.join('. ')}." + end + else + card = Card.new({ + :multiverse_id => imported_card['multiverseid'].to_s, + :name => imported_card['name'].to_s, + :card_set_id => card_set.id, + :layout => imported_card['layout'].to_s, + :mana_cost => imported_card['manaCost'].to_s, + :converted_mana_cost => imported_card['cmc'].to_s, + :colors => colors, + :card_type => imported_card['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card['flavor'].to_s, + :power => imported_card['power'].to_s, + :toughness => imported_card['toughness'].to_s, + :loyalty => imported_card['loyalty'].to_s, + :rarity => imported_card['rarity'].to_s, + :card_number => imported_card['number'].to_s, + :artist => imported_card['artist'].to_s + }) + + if card.save + puts "Created new card #{card.name}(#{card.multiverse_id})." + else + puts "Failed to create new card #{card.name}(#{card.multiverse_id}). #{card.errors.full_messages.join('. ')}." + end + end + + card + end + + imported_cards.select { |imported_card| %w(normal plane scheme phenomenon).include? imported_card['layout'] }.each do |imported_card| + card = cards.select { |card| card.multiverse_id == imported_card['multiverseid'].to_s }.first + + create_or_update_card(imported_card, card, card_set) + end + + imported_cards.select { |imported_card| imported_card['layout'] == 'split' }.group_by { |imported_card| imported_card['multiverseid'] }.each do |_, imported_card_parts| + card_name = imported_card_parts.first['names'].join(' // ') + + colors = imported_card_parts.inject([]) { |colors, imported_card_part| colors + (imported_card_part['colors'] || []) }.uniq + + card_supertypes = imported_card_parts.inject([]) { |supertypes, imported_card_part| supertypes + (imported_card_part['supertypes'] || []) }.uniq + + card_types = imported_card_parts.inject([]) { |types, imported_card_part| types + (imported_card_part['types'] || []) }.uniq + + card_subtypes = imported_card_parts.inject([]) { |subtypes, imported_card_part| subtypes + (imported_card_part['subtypes'] || []) }.uniq + + card_number = imported_card_parts.first['number'].gsub(/\D/, '') + + card = cards.select { |card| card.name == card_name }.first + + imported_card = { + 'multiverseid' => imported_card_parts.first['multiverseid'], + 'name' => card_name, + 'layout' => 'split', + 'manaCost' => '', + 'cmc' => '', + 'colors' => colors, + 'card_type' => '', + 'supertypes' => card_supertypes, + 'types' => card_types, + 'subtypes' => card_subtypes, + 'text' => '', + 'flavor' => '', + 'power' => '', + 'toughness' => '', + 'loyalty' => '', + 'rarity' => imported_card_parts.first['rarity'], + 'number' => card_number, + 'artist' => '' + } + + card = create_or_update_card(imported_card, card, card_set) + + imported_card_parts.each do |imported_card_part| + card_part = card.card_parts.select { |card_part| imported_card_part['name'] == card_part.name }.first + + colors = (imported_card_part['colors'] || []).join(';') + + card_supertypes = (imported_card_part['supertypes'] || []).join(';') + + card_types = (imported_card_part['types'] || []).join(';') + + card_subtypes = (imported_card_part['subtypes'] || []).join(';') + + if card_part.present? + if card_part.update_attributes({ + :name => imported_card_part['name'].to_s, + :layout => 'split', + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + puts "Updated existing card part #{card_part.id} #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to update existing card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + else + card_part = CardPart.new({ + :multiverse_id => imported_card_part['multiverseid'].to_s, + :name => imported_card_part['name'].to_s, + :card_id => card.id, + :layout => 'split', + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + + if card_part.save + puts "Created new card part #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to create new card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + end + end + end + + imported_cards.select { |imported_card| imported_card['layout'] == 'flip' }.group_by { |imported_card| imported_card['multiverseid'] }.each do |_, imported_card_parts| + card_name = imported_card_parts.first['names'].first + + colors = ((imported_card_parts.first['colors'] || []) + (imported_card_parts.last['colors'] || [])).uniq + + card_supertypes = ((imported_card_parts.first['supertypes'] || []) + (imported_card_parts.last['supertypes'] || [])).uniq + + card_types = ((imported_card_parts.first['types'] || []) + (imported_card_parts.last['types'] || [])).uniq + + card_subtypes = ((imported_card_parts.first['subtypes'] || []) + (imported_card_parts.last['subtypes'] || [])).uniq + + card_number = imported_card_parts.first['number'].gsub(/\D/, '') + + card = cards.select { |card| card.name == card_name }.first + + imported_card = { + 'multiverseid' => imported_card_parts.first['multiverseid'], + 'name' => card_name, + 'layout' => imported_card_parts.first['layout'], + 'manaCost' => '', + 'cmc' => '', + 'colors' => colors, + 'card_type' => '', + 'supertypes' => card_supertypes, + 'types' => card_types, + 'subtypes' => card_subtypes, + 'text' => '', + 'flavor' => '', + 'power' => '', + 'toughness' => '', + 'loyalty' => '', + 'rarity' => imported_card_parts.first['rarity'], + 'number' => card_number, + 'artist' => '' + } + + card = create_or_update_card(imported_card, card, card_set) + + imported_card_parts.each do |imported_card_part| + card_part = card.card_parts.select { |card_part| imported_card_part['name'] == card_part.name }.first + + colors = (imported_card_part['colors'] || []).join(';') + + card_supertypes = (imported_card_part['supertypes'] || []).join(';') + + card_types = (imported_card_part['types'] || []).join(';') + + card_subtypes = (imported_card_part['subtypes'] || []).join(';') + + if card_part.present? + if card_part.update_attributes({ + :name => imported_card_part['name'].to_s, + :layout => imported_card_part['layout'].to_s, + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + puts "Updated existing card part #{card_part.id} #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to update existing card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + else + card_part = CardPart.new({ + :multiverse_id => imported_card_part['multiverseid'].to_s, + :name => imported_card_part['name'].to_s, + :card_id => card.id, + :layout => imported_card_part['layout'].to_s, + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + + if card_part.save + puts "Created new card part #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to create new card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + end + end + end + + imported_cards.select { |imported_card| imported_card['layout'] == 'double-faced' }.group_by { |imported_card| imported_card['names'] }.each do |_, imported_card_parts| + card_name = imported_card_parts.first['names'].first + + colors = ((imported_card_parts.first['colors'] || []) + (imported_card_parts.last['colors'] || [])).uniq + + card_supertypes = ((imported_card_parts.first['supertypes'] || []) + (imported_card_parts.last['supertypes'] || [])).uniq + + card_types = ((imported_card_parts.first['types'] || []) + (imported_card_parts.last['types'] || [])).uniq + + card_subtypes = ((imported_card_parts.first['subtypes'] || []) + (imported_card_parts.last['subtypes'] || [])).uniq + + card_number = imported_card_parts.first['number'].gsub(/\D/, '') + + card = cards.select { |card| card.name == card_name }.first + + imported_card = { + 'multiverseid' => imported_card_parts.first['multiverseid'], + 'name' => card_name, + 'layout' => imported_card_parts.first['layout'], + 'manaCost' => '', + 'cmc' => '', + 'colors' => colors, + 'card_type' => '', + 'supertypes' => card_supertypes, + 'types' => card_types, + 'subtypes' => card_subtypes, + 'text' => '', + 'flavor' => '', + 'power' => '', + 'toughness' => '', + 'loyalty' => '', + 'rarity' => imported_card_parts.first['rarity'], + 'number' => card_number, + 'artist' => '' + } + + card = create_or_update_card(imported_card, card, card_set) + + imported_card_parts.each do |imported_card_part| + card_part = card.card_parts.select { |card_part| imported_card_part['name'] == card_part.name }.first + + colors = (imported_card_part['colors'] || []).join(';') + + card_supertypes = (imported_card_part['supertypes'] || []).join(';') + + card_types = (imported_card_part['types'] || []).join(';') + + card_subtypes = (imported_card_part['subtypes'] || []).join(';') + + if card_part.present? + if card_part.update_attributes({ + :name => imported_card_part['name'].to_s, + :layout => imported_card_part['layout'].to_s, + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + puts "Updated existing card part #{card_part.id} #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to update existing card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + else + card_part = CardPart.new({ + :multiverse_id => imported_card_part['multiverseid'].to_s, + :name => imported_card_part['name'].to_s, + :card_id => card.id, + :layout => imported_card_part['layout'].to_s, + :mana_cost => imported_card_part['manaCost'].to_s, + :converted_mana_cost => imported_card_part['cmc'].to_s, + :colors => colors, + :card_type => imported_card_part['type'].to_s, + :card_supertypes => card_supertypes, + :card_types => card_types, + :card_subtypes => card_subtypes, + :card_text => imported_card_part['text'].to_s.gsub("\n", '
'), + :flavor_text => imported_card_part['flavor'].to_s, + :power => imported_card_part['power'].to_s, + :toughness => imported_card_part['toughness'].to_s, + :loyalty => imported_card_part['loyalty'].to_s, + :rarity => imported_card_part['rarity'].to_s, + :card_number => imported_card_part['number'].to_s, + :artist => imported_card_part['artist'].to_s + }) + + if card_part.save + puts "Created new card part #{card_part.name}(#{card_part.multiverse_id})." + else + puts "Failed to create new card part #{card_part.name}(#{card_part.multiverse_id}). #{card_part.errors.full_messages.join('. ')}." + end + end + end + end + + imported_cards.select { |imported_card| imported_card['layout'] == 'token' }.each do |imported_card| + puts "Warning: Ignoring token card #{imported_card['name']}(#{imported_card['multiverseid']})." + end + end + end + + desc 'Clear the set data cache.' + task :clear_cache => :environment do + FileUtils.rm_rf(Rails.root.join('tmp', 'set_data')) + end end