Implement JobProcessor
Process job, job skill, and job accessory data from deck
This commit is contained in:
parent
e208e7daa9
commit
1dd8ae6159
2 changed files with 213 additions and 0 deletions
127
app/services/processors/job_processor.rb
Normal file
127
app/services/processors/job_processor.rb
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Processors
|
||||
##
|
||||
# JobProcessor is responsible for processing job data from the transformed deck data.
|
||||
# It finds a Job record by the master’s id and assigns it (and its job skills) to the Party.
|
||||
#
|
||||
# @example
|
||||
# raw_data = { 'job' => { "master": { "id": '130401', ... }, ... }, 'set_action': [ ... ] }
|
||||
# processor = Processors::JobProcessor.new(party, raw_data, language: 'en')
|
||||
# processor.process
|
||||
class JobProcessor < BaseProcessor
|
||||
##
|
||||
# Initializes a new JobProcessor.
|
||||
#
|
||||
# @param party [Party] the Party record.
|
||||
# @param data [Hash] the raw JSON data.
|
||||
# @param options [Hash] options hash; e.g. expects :language.
|
||||
def initialize(party, data, options = {})
|
||||
super(party, options)
|
||||
@party = party
|
||||
@data = data
|
||||
@language = options[:language] || 'en'
|
||||
end
|
||||
|
||||
##
|
||||
# Processes job data.
|
||||
#
|
||||
# Finds a Job record using a case‐insensitive search on +name_en+ or +name_jp+.
|
||||
# If found, it assigns the job to the party and (if provided) assigns subskills.
|
||||
#
|
||||
# @return [void]
|
||||
def process
|
||||
if @data.is_a?(Hash)
|
||||
@data = @data.with_indifferent_access
|
||||
else
|
||||
Rails.logger.error "[JOB] Invalid data format: expected a Hash, got #{@data.class}"
|
||||
return
|
||||
end
|
||||
|
||||
unless @data.key?('deck') && @data['deck'].key?('pc') && @data['deck']['pc'].key?('job')
|
||||
Rails.logger.error '[JOB] Missing job data in deck JSON'
|
||||
return
|
||||
end
|
||||
|
||||
# Extract job data
|
||||
job_data = @data.dig('deck', 'pc', 'job', 'master')
|
||||
job_skills = @data.dig('deck', 'pc', 'set_action')
|
||||
job_accessory_id = @data.dig('deck', 'pc', 'familiar_id') || @data.dig('deck', 'pc', 'shield_id')
|
||||
|
||||
# Look up and set the Job and its main skill
|
||||
process_core_job(job_data)
|
||||
|
||||
# Look up and set the job skills.
|
||||
if job_skills.present?
|
||||
skills = process_job_skills(job_skills)
|
||||
party.update(skill1: skills[0], skill2: skills[1], skill3: skills[2])
|
||||
end
|
||||
|
||||
# Look up and set the job accessory.
|
||||
accessory = process_job_accessory(job_accessory_id)
|
||||
party.update(accessory: accessory)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "[JOB] Exception during job processing: #{e.message}"
|
||||
raise e
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Updates the party with the corresponding job and its main skill.
|
||||
#
|
||||
# This method attempts to locate a Job using the provided job_data's 'id' (which represents
|
||||
# the granblue_id). If the job is found, it retrieves the job's main
|
||||
# skill (i.e. the JobSkill record where `main` is true) and updates the party with the job
|
||||
# and its main skill. If no job is found, the method returns without updating.
|
||||
#
|
||||
# @param [Hash] job_data A hash containing job information.
|
||||
# It must include the key 'id', which holds the granblue_id for the job.
|
||||
# @return [void]
|
||||
#
|
||||
# @example
|
||||
# job_data = { 'id' => 42 }
|
||||
# process_core_job(job_data)
|
||||
def process_core_job(job_data)
|
||||
# Look up the Job by granblue_id (the job master id).
|
||||
job = Job.find_by(granblue_id: job_data['id'])
|
||||
return unless job
|
||||
|
||||
main_skill = JobSkill.find_by(job_id: job.id, main: true)
|
||||
|
||||
party.update(job: job, skill0: main_skill)
|
||||
end
|
||||
|
||||
##
|
||||
# Processes and associates job skills with a given job.
|
||||
#
|
||||
# This method first removes any existing skills from the job. It then iterates over the provided
|
||||
# array of skill names, attempting to find a matching JobSkill record by comparing the provided
|
||||
# name against both the English and Japanese name fields. Any found JobSkill records are then
|
||||
# associated with the job. Finally, the method logs the processed job skill names.
|
||||
#
|
||||
# @param job_skills [Array<String>] an array of job skill names.
|
||||
# @return [Array<JobSkill>] an array of JobSkill records that were associated with the job.
|
||||
def process_job_skills(job_skills)
|
||||
job_skills.map do |skill|
|
||||
name = skill['name']
|
||||
JobSkill.find_by(name_en: name)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Processes raw data to find the currently set job accessory
|
||||
#
|
||||
# Searches JobAccessories for the given `granblue_id`
|
||||
#
|
||||
# @param accessory_id [String] the granblue_id of the accessory
|
||||
def process_job_accessory(accessory_id)
|
||||
JobAccessory.find_by(granblue_id: accessory_id)
|
||||
end
|
||||
|
||||
# Converts a value (string or boolean) to a boolean.
|
||||
def to_boolean(val)
|
||||
val.to_s.downcase == 'true'
|
||||
end
|
||||
end
|
||||
end
|
||||
86
spec/services/processors/job_processor_spec.rb
Normal file
86
spec/services/processors/job_processor_spec.rb
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Processors::JobProcessor, type: :model do
|
||||
let(:party) { create(:party) }
|
||||
# Use a job that has associated job skills.
|
||||
# In our seed/canonical data this job (by its ID) has several associated skills.
|
||||
let!(:job_record) { Job.find_by!(granblue_id: '130401') }
|
||||
|
||||
# Build the raw data hash that mimics the transformed structure.
|
||||
# The master section includes the job's basic information.
|
||||
# The param section includes level data and the subskills derived from the job's associated job skills.
|
||||
let(:deck_data) do
|
||||
file_path = Rails.root.join('spec', 'fixtures', 'deck_sample2.json')
|
||||
JSON.parse(File.read(file_path))
|
||||
end
|
||||
|
||||
subject { described_class.new(party, deck_data, language: 'en') }
|
||||
|
||||
context 'with valid job data' do
|
||||
it 'assigns the job to the party' do
|
||||
# Before processing, the party should not have a job.
|
||||
expect(party.job).to be_nil
|
||||
|
||||
# Process the job data.
|
||||
subject.process
|
||||
party.reload
|
||||
|
||||
# The party's job should now be set to the job_record.
|
||||
expect(party.job).to eq(job_record)
|
||||
end
|
||||
|
||||
it 'assigns the correct main skill to the party' do
|
||||
# Before processing, the party should not have a job.
|
||||
expect(party.job).to be_nil
|
||||
|
||||
# Process the job data.
|
||||
subject.process
|
||||
party.reload
|
||||
|
||||
main_skill = party.job.skills.where(main: true).first
|
||||
expect(party.skill0.id).to eq(main_skill.id)
|
||||
end
|
||||
|
||||
it 'associates the correct job skills' do
|
||||
# Before processing, the party should not have a job.
|
||||
expect(party.job).to be_nil
|
||||
|
||||
# Process the job data.
|
||||
subject.process
|
||||
party.reload
|
||||
|
||||
# We assume that the processor assigns up to four subskills to party attributes,
|
||||
# for example, party.skill0, party.skill1, etc.
|
||||
# Get the expected subskills (using order and taking the first four).
|
||||
data = deck_data.with_indifferent_access
|
||||
expected_subskills = data.dig('deck', 'pc', 'set_action').pluck(:name)
|
||||
actual_subskills = [party.skill1.name_en, party.skill2.name_en, party.skill3.name_en]
|
||||
expect(actual_subskills).to eq(expected_subskills)
|
||||
end
|
||||
|
||||
it 'assigns the correct accessory to the party' do
|
||||
# Process the job data.
|
||||
subject.process
|
||||
party.reload
|
||||
|
||||
expect(party.accessory.granblue_id).to eq(1.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid job data' do
|
||||
let(:invalid_data) { 'invalid data' }
|
||||
subject { described_class.new(party, invalid_data, language: 'en') }
|
||||
|
||||
it 'logs an error and does not assign a job' do
|
||||
expect { subject.process }.not_to(change { party.reload.job })
|
||||
end
|
||||
end
|
||||
|
||||
after(:each) do |example|
|
||||
if example.exception
|
||||
puts "\nDEBUG [JobProcessor]: #{example.full_description} failed with error: #{example.exception.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue