diff --git a/lecture_4/homework/app/controllers/clans/warriors_controller.rb b/lecture_4/homework/app/controllers/clans/warriors_controller.rb index 6ccf3ff9..acab2d6a 100644 --- a/lecture_4/homework/app/controllers/clans/warriors_controller.rb +++ b/lecture_4/homework/app/controllers/clans/warriors_controller.rb @@ -10,7 +10,7 @@ def index warriors = clan.warriors if params.key?(:alive) - if params[:alive].to_i == 0 + if params[:alive].to_i.zero? render json: warriors.dead else render json: warriors.alive diff --git a/lecture_4/homework/app/controllers/mercenaries_controller.rb b/lecture_4/homework/app/controllers/mercenaries_controller.rb index 4382cfcf..d1206231 100644 --- a/lecture_4/homework/app/controllers/mercenaries_controller.rb +++ b/lecture_4/homework/app/controllers/mercenaries_controller.rb @@ -3,7 +3,7 @@ # This controller is written badly on purpose. Please refactor this class MercenariesController < ApplicationController def index - render json: Mercenary.where('available_from < ?', Time.now).all + render json: Mercenary.available end def show @@ -11,17 +11,14 @@ def show end def employ_best - mercenary = Mercenary.where('available_from < ?', Time.now).order(price: :asc).first # TODO: what about experience? - clan = find_clan - building = find_building - warrior_class = clan.warriors.select('type, count(type) as warriors_count').group(:type).order('warriors_count ASC').first.class - warrior = warrior_class.create!(name: mercenary.name, clan: clan, building: building, preferred_weapon_kind: mercenary.preferred_weapon_kind, mercenary: mercenary) - create_good_weapon(mercenary) + mercenary = best + warrior = WarriorRecruiter.new(mercenary: mercenary, params: mercenary_params).call render json: warrior, include: [:mercenary], status: 201 end def employ return unless mercenary.available_from < Time.now + clan = find_clan building = find_building warrior_class = clan.warriors.select('type, count(type) as warriors_count').group(:type).order('warriors_count ASC').first.class @@ -37,9 +34,7 @@ def mercenary end def find_building - if params[:building_id] - Building.find(params[:building_id]) - end + Building.find(params[:building_id]) if params[:building_id] end def find_clan @@ -50,14 +45,11 @@ def find_clan end end - def create_good_weapon(mercenary) - case mercenary.preferred_weapon_kind - when :melee - Weapons::Katana.create!(warrior: mercenary.warrior, range: 2, damage: 25) - when :ranged - Weapons::Musket.create!(warrior: mercenary.warrior, range: 40, damage: 10) - else - # TODO: some default? - end + def best + Mercenary.available.cheapest + end + + def mercenary_params + params.permit(:building_id, :clan_id) end end diff --git a/lecture_4/homework/app/models/mercenary.rb b/lecture_4/homework/app/models/mercenary.rb index 82aa4bdd..6787ce14 100644 --- a/lecture_4/homework/app/models/mercenary.rb +++ b/lecture_4/homework/app/models/mercenary.rb @@ -11,6 +11,8 @@ class Mercenary < ApplicationRecord validates :warrior, uniqueness: { allow_nil: true } + scope :available, -> { MercenariesQuery.available } + scope :cheapest, -> { MercenariesQuery.cheapest } scope :alive, -> { where('death_date IS NULL') } scope :dead, -> { where('death_date IS NOT NULL') } end diff --git a/lecture_4/homework/app/models/warriors/hussar.rb b/lecture_4/homework/app/models/warriors/hussar.rb index c277ff70..0dee7137 100644 --- a/lecture_4/homework/app/models/warriors/hussar.rb +++ b/lecture_4/homework/app/models/warriors/hussar.rb @@ -5,7 +5,7 @@ class Hussar < Warrior attribute :preferred_weapon_kind, :string, default: :ranged def attack - "Hussar #{name} charged while yielding #{weapon.type.demodulize}" + "Hussar #{name} charged while yielding #{(weapon || NullWeapon.new).type.demodulize}" end end end diff --git a/lecture_4/homework/app/models/warriors/samurai.rb b/lecture_4/homework/app/models/warriors/samurai.rb index 2b477760..cd37cc06 100644 --- a/lecture_4/homework/app/models/warriors/samurai.rb +++ b/lecture_4/homework/app/models/warriors/samurai.rb @@ -5,7 +5,7 @@ class Samurai < Warrior attribute :preferred_weapon_kind, :string, default: :melee def attack - "Samurai #{name} attacked with #{weapon.type.demodulize}" + "Samurai #{name} attacked with #{(weapon || NullWeapon.new).type.demodulize}" end end end diff --git a/lecture_4/homework/app/models/weapons/rod.rb b/lecture_4/homework/app/models/weapons/rod.rb new file mode 100644 index 00000000..f330469a --- /dev/null +++ b/lecture_4/homework/app/models/weapons/rod.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Weapons + class Rod < Weapon + attribute :kind, :string, default: :magical + + def calculate_damage(distance) + distance < range ? damage * 2 : damage / 2 + 42 + end + end +end diff --git a/lecture_4/homework/app/null_objects/weapon.rb b/lecture_4/homework/app/null_objects/weapon.rb new file mode 100644 index 00000000..c6a5c75c --- /dev/null +++ b/lecture_4/homework/app/null_objects/weapon.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class NullWeapon + def type + 'Null Weapon' + end +end diff --git a/lecture_4/homework/app/queries/mercenaries_query.rb b/lecture_4/homework/app/queries/mercenaries_query.rb new file mode 100644 index 00000000..65a62d1c --- /dev/null +++ b/lecture_4/homework/app/queries/mercenaries_query.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class MercenariesQuery + def self.available(relation: Mercenary) + relation.where(warrior_id: nil).where('available_from < ?', Time.now) + end + + def self.cheapest(relation: Mercenary) + relation.order(price: :asc).first + end +end diff --git a/lecture_4/homework/app/services/warrior_recruiter.rb b/lecture_4/homework/app/services/warrior_recruiter.rb new file mode 100644 index 00000000..1fb02149 --- /dev/null +++ b/lecture_4/homework/app/services/warrior_recruiter.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class WarriorRecruiter + attr_accessor :mercenary, :params + def initialize(mercenary:, params:) + @mercenary = mercenary + @params = params + end + + def call + clan = find_clan + building = find_building + warrior_class = find_class(clan) + + warrior = warrior_class.create!( + name: mercenary.name, + clan: clan, + building: building, + preferred_weapon_kind: mercenary.preferred_weapon_kind, + mercenary: @mercenary + ) + create_good_weapon(@mercenary) + warrior + end + + private + + def find_class(clan) + clan.warriors.select('type, count(type) as warriors_count') + .group(:type).order('warriors_count ASC').first.class + end + + def find_building + Building.find(@params[:building_id]) if @params[:building_id] + end + + def find_clan + if @params[:clan_id] + Clan.find(@params[:clan_id]) + else + Clan.order(warriors_count: :desc).first + end + end + + def create_good_weapon(mercenary) + WeaponCreator.new(mercenary: mercenary).call + end +end diff --git a/lecture_4/homework/app/services/weapon_creator.rb b/lecture_4/homework/app/services/weapon_creator.rb new file mode 100644 index 00000000..d8e346d6 --- /dev/null +++ b/lecture_4/homework/app/services/weapon_creator.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class WeaponCreator + attr_accessor :mercenary + + def initialize(mercenary:) + @mercenary = mercenary + end + + def call(mercenary) + case mercenary.preferred_weapon_kind + when :melee + Weapons::Katana.create!(warrior: mercenary.warrior, range: 2, damage: 25) + when :ranged + Weapons::Musket.create!(warrior: mercenary.warrior, range: 40, damage: 10) + else + Weapons::Rod.create!(warrior: mercenary.warrior, range: 42, damage: 10) + end + end +end diff --git a/recruitment_task/converter.rb b/recruitment_task/converter.rb index 1a91dec6..a8e23fe1 100644 --- a/recruitment_task/converter.rb +++ b/recruitment_task/converter.rb @@ -1,5 +1,23 @@ require_relative 'price' -class Converter - # TODO: Implement me +class Converter < Price + + EXCHANGE = { + eur: { pln: 4.32, eur: 1.00, usd: 1.13 }, + usd: { pln: 3.81, eur: 0.88, usd: 1.00 }, + pln: { pln: 1.00, eur: 0.23, usd: 0.26 } + }.freeze + + def initialize(price) + @price = price + end + + def convert_to(currency) + validate_currency(currency) + (@price.amount * EXCHANGE[@price.currency][currency]).round(2) + end + + def validate_currency(currency) + raise InvalidCurrency unless SUPPORTED_CURRENCIES.include?(currency) + end end