diff --git a/lecture_2/homework/Gemfile b/lecture_2/homework/Gemfile index f3629b6f..37cb875c 100644 --- a/lecture_2/homework/Gemfile +++ b/lecture_2/homework/Gemfile @@ -9,6 +9,7 @@ gem 'rails', '~> 5.2.2' gem 'pg' # Use Puma as the app server gem 'puma', '~> 3.11' +gem 'active_model_serializers' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # gem 'jbuilder', '~> 2.5' # Use Redis adapter to run Action Cable in production diff --git a/lecture_2/homework/Gemfile.lock b/lecture_2/homework/Gemfile.lock index ae757417..5c97cd71 100644 --- a/lecture_2/homework/Gemfile.lock +++ b/lecture_2/homework/Gemfile.lock @@ -24,6 +24,11 @@ GEM erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) + active_model_serializers (0.10.9) + actionpack (>= 4.1, < 6) + activemodel (>= 4.1, < 6) + case_transform (>= 0.2) + jsonapi-renderer (>= 0.1.1.beta1, < 0.3) activejob (5.2.2.1) activesupport (= 5.2.2.1) globalid (>= 0.3.6) @@ -47,6 +52,8 @@ GEM msgpack (~> 1.0) builder (3.2.3) byebug (11.0.0) + case_transform (0.2) + activesupport concurrent-ruby (1.1.5) crass (1.0.4) erubi (1.8.0) @@ -55,6 +62,7 @@ GEM activesupport (>= 4.2.0) i18n (1.6.0) concurrent-ruby (~> 1.0) + jsonapi-renderer (0.2.0) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -133,6 +141,7 @@ PLATFORMS ruby DEPENDENCIES + active_model_serializers bootsnap (>= 1.1.0) byebug listen (>= 3.0.5, < 3.2) diff --git a/lecture_2/homework/app/controllers/application_controller.rb b/lecture_2/homework/app/controllers/application_controller.rb index 891a2573..f8080e94 100644 --- a/lecture_2/homework/app/controllers/application_controller.rb +++ b/lecture_2/homework/app/controllers/application_controller.rb @@ -1,9 +1,3 @@ class ApplicationController < ActionController::API - rescue_from ActiveRecord::RecordNotFound do |exception| - render json: { message: exception.message }, status: 404 - end - - rescue_from ActiveRecord::RecordInvalid do |exception| - render json: { message: exception.message }, status: 422 - end + include ErrorsHandler end diff --git a/lecture_2/homework/app/controllers/clans/samurais_controller.rb b/lecture_2/homework/app/controllers/clans/samurais_controller.rb deleted file mode 100644 index 59fef45f..00000000 --- a/lecture_2/homework/app/controllers/clans/samurais_controller.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Clans - class SamuraisController < ApplicationController - def show - render json: samurai.to_json - end - - def index - samurais = clan.samurais - if params.key?(:alive) - if params[:alive].to_i == 0 - render json: samurais.dead.to_json - else - render json: samurais.alive.to_json - end - else - render json: samurais.to_json - end - end - - def create - samurai = clan.samurais.create!(samurai_params) - - render json: samurai.to_json, status: 201 - end - - def update - samurai.update!(samurai_params) - - render json: samurai.to_json - end - - def destroy - samurai.destroy! - end - - private - - def clan - @clan ||= Clan.find(params[:clan_id]) - end - - def samurai - @samurai ||= Samurai.find_by!(id: params[:id], clan_id: params[:clan_id]) - end - - def samurai_params - params.permit(:name, :death_date, :armor_quality, :number_of_battles, :join_date) - end - end -end diff --git a/lecture_2/homework/app/controllers/clans/warriors_controller.rb b/lecture_2/homework/app/controllers/clans/warriors_controller.rb new file mode 100644 index 00000000..cb4e97d0 --- /dev/null +++ b/lecture_2/homework/app/controllers/clans/warriors_controller.rb @@ -0,0 +1,50 @@ +module Clans + class WarriorsController < ApplicationController + def show + render json: warrior, serializer: Clans::WarriorSerializer + end + + def index + warriors = clan.warriors + if params.key?(:alive) + if params[:alive].to_i == 0 + render json: warriors.dead, each_serializer: Clans::WarriorSerializer + else + render json: warriors, each_serializer: Clans::WarriorSerializer + end + else + render json: warriors + end + end + + def create + warrior = clan.warriors.create!(warrior_params) + + render json: warrior.to_json, status: 201 + end + + def update + warrior.update!(warrior_params) + + render json: warrior.to_json + end + + def destroy + warrior.destroy! + end + + private + + def clan + @clan ||= Clan.find(params[:clan_id]) + end + + def warrior + @warrior ||= Warrior.find_by!(id: params[:id], clan_id: params[:clan_id]) + end + + def warrior_params + params.permit(:name, :death_date, :armor_quality, :number_of_battles, :join_date, :type) + end + end +end diff --git a/lecture_2/homework/app/controllers/clans_controller.rb b/lecture_2/homework/app/controllers/clans_controller.rb index 37db247b..77ab07b9 100644 --- a/lecture_2/homework/app/controllers/clans_controller.rb +++ b/lecture_2/homework/app/controllers/clans_controller.rb @@ -1,6 +1,6 @@ class ClansController < ApplicationController def index - render json: Clan.all.to_json + render json: Clan.all, each_serializer: ClanSerializer end def create diff --git a/lecture_2/homework/app/controllers/concerns/errors_handler.rb b/lecture_2/homework/app/controllers/concerns/errors_handler.rb new file mode 100644 index 00000000..256ab7b1 --- /dev/null +++ b/lecture_2/homework/app/controllers/concerns/errors_handler.rb @@ -0,0 +1,13 @@ +module ErrorsHandler + extend ActiveSupport::Concern + + included do + rescue_from ActiveRecord::RecordInvalid do |exception| + render json: exception.record.errors, status: :unprocessable_entity + end + + rescue_from ActiveRecord::RecordNotFound do |exception| + render json: { error: exception.message }, status: :not_found + end + end +end diff --git a/lecture_2/homework/app/models/archer.rb b/lecture_2/homework/app/models/archer.rb new file mode 100644 index 00000000..f5c36681 --- /dev/null +++ b/lecture_2/homework/app/models/archer.rb @@ -0,0 +1,5 @@ +class Archer < Warrior + def attack + "I aim for the knee. Always." + end +end diff --git a/lecture_2/homework/app/models/bpp.rb b/lecture_2/homework/app/models/bpp.rb new file mode 100644 index 00000000..61ed9b9d --- /dev/null +++ b/lecture_2/homework/app/models/bpp.rb @@ -0,0 +1,5 @@ +class BPP < Warrior + def attack + "..." + end +end diff --git a/lecture_2/homework/app/models/castle.rb b/lecture_2/homework/app/models/castle.rb new file mode 100644 index 00000000..1ad01f7a --- /dev/null +++ b/lecture_2/homework/app/models/castle.rb @@ -0,0 +1,3 @@ +class Castle < ApplicationRecord + has_many :warriors, as: :defensible +end diff --git a/lecture_2/homework/app/models/clan.rb b/lecture_2/homework/app/models/clan.rb index 71f4e9de..ae51d1a2 100644 --- a/lecture_2/homework/app/models/clan.rb +++ b/lecture_2/homework/app/models/clan.rb @@ -1,4 +1,4 @@ class Clan < ApplicationRecord - has_many :samurais, dependent: :destroy - validates :name, presence: true + has_many :warriors, dependent: :destroy + validates :name, presence: true, uniqueness: true end diff --git a/lecture_2/homework/app/models/husarz.rb b/lecture_2/homework/app/models/husarz.rb new file mode 100644 index 00000000..ed824c02 --- /dev/null +++ b/lecture_2/homework/app/models/husarz.rb @@ -0,0 +1,5 @@ +class Husarz < Warrior + def attack + "Na pohybel!" + end +end diff --git a/lecture_2/homework/app/models/magical.rb b/lecture_2/homework/app/models/magical.rb new file mode 100644 index 00000000..da699f57 --- /dev/null +++ b/lecture_2/homework/app/models/magical.rb @@ -0,0 +1,6 @@ +class Magical < Weapon + def calculate_damage(distance) + return 0 if distance > range * Random.rand(100) + damage * Random.rand(100) + end +end diff --git a/lecture_2/homework/app/models/melee.rb b/lecture_2/homework/app/models/melee.rb new file mode 100644 index 00000000..604dafe8 --- /dev/null +++ b/lecture_2/homework/app/models/melee.rb @@ -0,0 +1,6 @@ +class Melee < Weapon + def calculate_damage(distance) + return 0 if distance > range + damage + end +end diff --git a/lecture_2/homework/app/models/ranged.rb b/lecture_2/homework/app/models/ranged.rb new file mode 100644 index 00000000..4145d1f6 --- /dev/null +++ b/lecture_2/homework/app/models/ranged.rb @@ -0,0 +1,7 @@ +class Ranged < Weapon + def calculate_damage(distance) + return 0 if distance > range + f =( (range.to_f/2) - distance.to_f)**2/((range.to_f/2)**2) + (damage * (1- f)).to_i + end +end diff --git a/lecture_2/homework/app/models/samurai.rb b/lecture_2/homework/app/models/samurai.rb index b2fafebb..d2adcbc1 100644 --- a/lecture_2/homework/app/models/samurai.rb +++ b/lecture_2/homework/app/models/samurai.rb @@ -1,11 +1,5 @@ -class Samurai < ApplicationRecord - belongs_to :clan - - validates :name, presence: true - validates :armor_quality, numericality: { only_integer: true, - greater_than_or_equal_to: 0, - less_than_or_equal_to: 100 } - - scope :alive, -> { where('death_date IS NULL') } - scope :dead, -> { where('death_date IS NOT NULL') } +class Samurai < Warrior + def attack + "Nani?!" + end end diff --git a/lecture_2/homework/app/models/tower.rb b/lecture_2/homework/app/models/tower.rb new file mode 100644 index 00000000..f14e679e --- /dev/null +++ b/lecture_2/homework/app/models/tower.rb @@ -0,0 +1,3 @@ +class Tower < ApplicationRecord + has_many :warriors, as: :defensible +end diff --git a/lecture_2/homework/app/models/wall.rb b/lecture_2/homework/app/models/wall.rb new file mode 100644 index 00000000..170dc87b --- /dev/null +++ b/lecture_2/homework/app/models/wall.rb @@ -0,0 +1,3 @@ +class Wall < ApplicationRecord + has_many :warriors, as: :defensible +end diff --git a/lecture_2/homework/app/models/warrior.rb b/lecture_2/homework/app/models/warrior.rb new file mode 100644 index 00000000..045dbe78 --- /dev/null +++ b/lecture_2/homework/app/models/warrior.rb @@ -0,0 +1,16 @@ +class Warrior < ApplicationRecord + belongs_to :clan + belongs_to :defensible, polymorphic: true + has_one :weapon, dependent: :destroy + validates :name, presence: true, uniqueness: {conditions: -> {where('death_date IS NULL')}} + validates :armor_quality, numericality: { only_integer: true, + greater_than_or_equal_to: 0, + less_than_or_equal_to: 100 } + + scope :alive, -> { where('death_date IS NULL') } + scope :dead, -> { where('death_date IS NOT NULL') } + + def attack + "Ugabuga!" + end +end diff --git a/lecture_2/homework/app/models/weapon.rb b/lecture_2/homework/app/models/weapon.rb new file mode 100644 index 00000000..1412e97e --- /dev/null +++ b/lecture_2/homework/app/models/weapon.rb @@ -0,0 +1,11 @@ +class Weapon < ApplicationRecord + belongs_to :warrior + + validates :damage, :range, presence: true, numericality: { only_integer: true, + greater_than_or_equal_to: 0, + less_than_or_equal_to: 100 } + + def calculate_damage(distance) + 0 + end +end diff --git a/lecture_2/homework/app/serializers/clan_serializer.rb b/lecture_2/homework/app/serializers/clan_serializer.rb new file mode 100644 index 00000000..a3cbbe52 --- /dev/null +++ b/lecture_2/homework/app/serializers/clan_serializer.rb @@ -0,0 +1,11 @@ +class ClanSerializer < ActiveModel::Serializer + attributes :name, :warriors + def warriors + object.warriors.map do |warrior| + { + name: warrior.name, + type: warrior.type} + end + end + +end diff --git a/lecture_2/homework/app/serializers/warrior_serializer.rb b/lecture_2/homework/app/serializers/warrior_serializer.rb new file mode 100644 index 00000000..ecb2807e --- /dev/null +++ b/lecture_2/homework/app/serializers/warrior_serializer.rb @@ -0,0 +1,23 @@ + + class WarriorSerializer < ActiveModel::Serializer + attributes :id, :name, :defensible, :weapon, :attack + + def weapon + d = object.weapon + if d.nil? then + return nil + end + { type: d.type, + range: d.range, + damage: d.damage} + + end + + def defensible + d = object.defensible + { type: d.class.table_name, + name: d.name} + + end + + end diff --git a/lecture_2/homework/config/application.rb b/lecture_2/homework/config/application.rb index f5390e33..0683b65c 100644 --- a/lecture_2/homework/config/application.rb +++ b/lecture_2/homework/config/application.rb @@ -21,7 +21,7 @@ module Lecture1 class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 5.2 - + ActiveModelSerializers.config.adapter = :json_api # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading diff --git a/lecture_2/homework/config/database.yml b/lecture_2/homework/config/database.yml index 965a5d46..ed14f214 100644 --- a/lecture_2/homework/config/database.yml +++ b/lecture_2/homework/config/database.yml @@ -9,3 +9,5 @@ development: test: &test <<: *default database: flode_test + host: localhost + diff --git a/lecture_2/homework/config/routes.rb b/lecture_2/homework/config/routes.rb index 9c03cd59..15d90bee 100644 --- a/lecture_2/homework/config/routes.rb +++ b/lecture_2/homework/config/routes.rb @@ -1,5 +1,7 @@ Rails.application.routes.draw do resources :clans, only: %i[index create] do - resources :samurais, module: :clans + resources :warriors, module: :clans end + + end diff --git a/lecture_2/homework/db/migrate/20190314150921_create_clans_and_samurais.rb b/lecture_2/homework/db/migrate/20190314150921_create_clans_and_samurais.rb index ebfc3158..f7cb64d9 100644 --- a/lecture_2/homework/db/migrate/20190314150921_create_clans_and_samurais.rb +++ b/lecture_2/homework/db/migrate/20190314150921_create_clans_and_samurais.rb @@ -1,7 +1,7 @@ class CreateClansAndSamurais < ActiveRecord::Migration[5.2] def change create_table :clans do |t| - t.string :name, null: false + t.string :name, null: false, uniquness: true t.timestamps end diff --git a/lecture_2/homework/db/migrate/20190408195814_change_samurais_to_warriors.rb b/lecture_2/homework/db/migrate/20190408195814_change_samurais_to_warriors.rb new file mode 100644 index 00000000..fcacaac2 --- /dev/null +++ b/lecture_2/homework/db/migrate/20190408195814_change_samurais_to_warriors.rb @@ -0,0 +1,6 @@ +class ChangeSamuraisToWarriors < ActiveRecord::Migration[5.2] + def change + rename_table :samurais, :warriors + add_column :warriors, :type, :string, default: 'Samurai' + end +end diff --git a/lecture_2/homework/db/migrate/20190408202445_add_defensible_to_warriors.rb b/lecture_2/homework/db/migrate/20190408202445_add_defensible_to_warriors.rb new file mode 100644 index 00000000..d5e48ed0 --- /dev/null +++ b/lecture_2/homework/db/migrate/20190408202445_add_defensible_to_warriors.rb @@ -0,0 +1,5 @@ +class AddDefensibleToWarriors < ActiveRecord::Migration[5.2] + def change + add_reference :warriors, :defensible, polymorphic: true, index: true + end +end diff --git a/lecture_2/homework/db/migrate/20190408202703_create_walls.rb b/lecture_2/homework/db/migrate/20190408202703_create_walls.rb new file mode 100644 index 00000000..4549f122 --- /dev/null +++ b/lecture_2/homework/db/migrate/20190408202703_create_walls.rb @@ -0,0 +1,17 @@ +class CreateWalls < ActiveRecord::Migration[5.2] + def change + create_table :walls do |t| + t.string :name, null: false + t.timestamps + end + create_table :towers do |t| + t.string :name, null: false + t.timestamps + end + create_table :castles do |t| + t.string :name, null: false + t.timestamps + end + + end +end diff --git a/lecture_2/homework/db/migrate/20190408213939_add_weapon_to_warrior.rb b/lecture_2/homework/db/migrate/20190408213939_add_weapon_to_warrior.rb new file mode 100644 index 00000000..1e3019ee --- /dev/null +++ b/lecture_2/homework/db/migrate/20190408213939_add_weapon_to_warrior.rb @@ -0,0 +1,14 @@ +class AddWeaponToWarrior < ActiveRecord::Migration[5.2] + def change + + create_table :weapons do |t| + t.integer :damage, null: false + t.integer :range, null: false + t.timestamps + t.string :type, null: false + end + + add_reference :weapons, :warrior + + end +end diff --git a/lecture_2/homework/db/schema.rb b/lecture_2/homework/db/schema.rb index 98a644ef..fdb2884d 100644 --- a/lecture_2/homework/db/schema.rb +++ b/lecture_2/homework/db/schema.rb @@ -10,18 +10,36 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_03_14_150921) do +ActiveRecord::Schema.define(version: 2019_04_08_213939) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "castles", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "clans", force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end - create_table "samurais", force: :cascade do |t| + create_table "towers", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "walls", force: :cascade do |t| + t.string "name", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "warriors", force: :cascade do |t| t.string "name", null: false t.integer "armor_quality", default: 0 t.integer "number_of_battles", default: 0 @@ -30,8 +48,22 @@ t.bigint "clan_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["clan_id"], name: "index_samurais_on_clan_id" + t.string "type", default: "Samurai" + t.string "defensible_type" + t.bigint "defensible_id" + t.index ["clan_id"], name: "index_warriors_on_clan_id" + t.index ["defensible_type", "defensible_id"], name: "index_warriors_on_defensible_type_and_defensible_id" + end + + create_table "weapons", force: :cascade do |t| + t.integer "damage", null: false + t.integer "range", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "type", null: false + t.bigint "warrior_id" + t.index ["warrior_id"], name: "index_weapons_on_warrior_id" end - add_foreign_key "samurais", "clans" + add_foreign_key "warriors", "clans" end diff --git a/lecture_2/homework/test/controllers/defensible_controller_test.rb b/lecture_2/homework/test/controllers/defensible_controller_test.rb new file mode 100644 index 00000000..089aa1b0 --- /dev/null +++ b/lecture_2/homework/test/controllers/defensible_controller_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class DefensibleControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/lecture_2/homework/test/fixtures/walls.yml b/lecture_2/homework/test/fixtures/walls.yml new file mode 100644 index 00000000..80aed36e --- /dev/null +++ b/lecture_2/homework/test/fixtures/walls.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/lecture_2/homework/test/fixtures/warriors.yml b/lecture_2/homework/test/fixtures/warriors.yml new file mode 100644 index 00000000..80aed36e --- /dev/null +++ b/lecture_2/homework/test/fixtures/warriors.yml @@ -0,0 +1,11 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +# This model initially had no columns defined. If you add columns to the +# model remove the '{}' from the fixture names and add the columns immediately +# below each fixture, per the syntax in the comments below +# +one: {} +# column: value +# +two: {} +# column: value diff --git a/lecture_2/homework/test/models/wall_test.rb b/lecture_2/homework/test/models/wall_test.rb new file mode 100644 index 00000000..c3c493fe --- /dev/null +++ b/lecture_2/homework/test/models/wall_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class WallTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/lecture_2/homework/test/models/warrior_test.rb b/lecture_2/homework/test/models/warrior_test.rb new file mode 100644 index 00000000..019dccd2 --- /dev/null +++ b/lecture_2/homework/test/models/warrior_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class WarriorTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/lecture_4/homework/app/controllers/concerns/errors_handler.rb b/lecture_4/homework/app/controllers/concerns/errors_handler.rb index 3eb31dfe..256ab7b1 100644 --- a/lecture_4/homework/app/controllers/concerns/errors_handler.rb +++ b/lecture_4/homework/app/controllers/concerns/errors_handler.rb @@ -1,5 +1,3 @@ -# frozen_string_literal: true - module ErrorsHandler extend ActiveSupport::Concern diff --git a/lecture_4/homework/app/models/warrior.rb b/lecture_4/homework/app/models/warrior.rb index eacdc13f..7192d825 100644 --- a/lecture_4/homework/app/models/warrior.rb +++ b/lecture_4/homework/app/models/warrior.rb @@ -14,4 +14,11 @@ class Warrior < ApplicationRecord scope :alive, -> { where('death_date IS NULL') } scope :dead, -> { where('death_date IS NOT NULL') } + + def mercenary + super || NullMercenary.new + end + def weapon + super || NullWeapon.new(preferred_weapon_kind) + end end diff --git a/lecture_4/homework/app/models/warriors/hussar.rb b/lecture_4/homework/app/models/warriors/hussar.rb index c277ff70..fc02d6cb 100644 --- a/lecture_4/homework/app/models/warriors/hussar.rb +++ b/lecture_4/homework/app/models/warriors/hussar.rb @@ -5,7 +5,10 @@ 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.type.demodulize }" end + + end end diff --git a/lecture_4/homework/app/nulls/null_mercenary.rb b/lecture_4/homework/app/nulls/null_mercenary.rb new file mode 100644 index 00000000..2faf8024 --- /dev/null +++ b/lecture_4/homework/app/nulls/null_mercenary.rb @@ -0,0 +1,18 @@ +class NullMercenary + + def name + end + + def experience + end + + def preferred_weapon_kind + end + + def available_from + end + + def price + end + +end \ No newline at end of file diff --git a/lecture_4/homework/app/nulls/null_weapon.rb b/lecture_4/homework/app/nulls/null_weapon.rb new file mode 100644 index 00000000..ed9d2da5 --- /dev/null +++ b/lecture_4/homework/app/nulls/null_weapon.rb @@ -0,0 +1,26 @@ +class NullWeapon + + def initialize(pref_kind) + @kind= pref_kind + end + + def type + "no weapon" + end + + def range + 0 + end + + def damage + 0 + end + + def kind + @kind + end + + def calculate_damage + 0 + end +end \ No newline at end of file diff --git a/lecture_5/homework/.rspec b/lecture_5/homework/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/lecture_5/homework/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/lecture_5/homework/Gemfile b/lecture_5/homework/Gemfile index f10bbf07..fc534ef7 100644 --- a/lecture_5/homework/Gemfile +++ b/lecture_5/homework/Gemfile @@ -48,6 +48,7 @@ end group :test do gem 'rspec-rails' + gem 'factory_bot_rails' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/lecture_5/homework/Gemfile.lock b/lecture_5/homework/Gemfile.lock index 411593e8..c9bcce96 100644 --- a/lecture_5/homework/Gemfile.lock +++ b/lecture_5/homework/Gemfile.lock @@ -59,6 +59,11 @@ GEM crass (1.0.4) diff-lcs (1.3) erubi (1.8.0) + factory_bot (5.0.2) + activesupport (>= 4.2.0) + factory_bot_rails (5.0.2) + factory_bot (~> 5.0.2) + railties (>= 4.2.0) ffi (1.10.0) globalid (0.4.2) activesupport (>= 4.2.0) @@ -179,6 +184,7 @@ DEPENDENCIES active_model_serializers bootsnap (>= 1.1.0) byebug + factory_bot_rails listen (>= 3.0.5, < 3.2) pg puma (~> 3.11) diff --git a/lecture_5/homework/app/controllers/buildings_controller.rb b/lecture_5/homework/app/controllers/buildings_controller.rb index 9b7e9677..39bef991 100644 --- a/lecture_5/homework/app/controllers/buildings_controller.rb +++ b/lecture_5/homework/app/controllers/buildings_controller.rb @@ -1,7 +1,17 @@ # frozen_string_literal: true class BuildingsController < ApplicationController - def index; end + def index + render json: Building.all + end - def show; end + def show + render json: building + end + + private + + def building + @building || Building.find(params[:id]) + end end diff --git a/lecture_5/homework/app/serializers/building_serializer.rb b/lecture_5/homework/app/serializers/building_serializer.rb index 899c9aa0..b594e331 100644 --- a/lecture_5/homework/app/serializers/building_serializer.rb +++ b/lecture_5/homework/app/serializers/building_serializer.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true class BuildingSerializer < ActiveModel::Serializer - attributes :name + attributes :name, :granary, :siege_ability has_many :warriors + + def siege_ability + Reports::SiegeReport.new(building: object).call + end end diff --git a/lecture_5/homework/app/services/reports/siege_report.rb b/lecture_5/homework/app/services/reports/siege_report.rb index 16a0b3a4..4c919daa 100644 --- a/lecture_5/homework/app/services/reports/siege_report.rb +++ b/lecture_5/homework/app/services/reports/siege_report.rb @@ -3,10 +3,21 @@ module Reports class SiegeReport def initialize(building:) + @building = building end def call - raise NotImprementedYet + return 0 if @building.warriors.all.empty? + + consume_rate = 10 + @building.warriors.each do |warrior| + consume_rate += if warrior.type == 'Warriors::Hussar' + 2 + else + 1 + end + end + @building.granary / consume_rate end end end diff --git a/lecture_5/homework/db/schema.rb b/lecture_5/homework/db/schema.rb index 13f968ab..579f0b47 100644 --- a/lecture_5/homework/db/schema.rb +++ b/lecture_5/homework/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_04_05_090544) do +ActiveRecord::Schema.define(version: 2019_04_14_111449) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -20,6 +20,7 @@ t.string "type", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "granary", default: 0, null: false end create_table "clans", force: :cascade do |t| diff --git a/lecture_5/homework/spec/factories.rb b/lecture_5/homework/spec/factories.rb new file mode 100644 index 00000000..bbf8de77 --- /dev/null +++ b/lecture_5/homework/spec/factories.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :building do + name { 'default' } + type { 'Buildings::Walls' } + granary { 0 } + end +end + +FactoryBot.define do + factory :warrior, class: 'Warrior' do + association(:building) + association(:clan) + name { 'default' } + end + + factory :samurai, parent: :warrior, class: 'Warriors::Samurai' do + end + + factory :hussar, parent: :warrior, class: 'Warriors::Hussar' do + end +end + +FactoryBot.define do + factory :clan do + name { 'default' } + end +end diff --git a/lecture_5/homework/spec/rails_helper.rb b/lecture_5/homework/spec/rails_helper.rb new file mode 100644 index 00000000..ff834d08 --- /dev/null +++ b/lecture_5/homework/spec/rails_helper.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../config/environment', __dir__) +# Prevent database truncation if the environment is production +abort('The Rails environment is running in production mode!') if Rails.env.production? +require 'rspec/rails' +require 'support/factory_bot' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } + +# Checks for pending migrations and applies them before tests are run. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + puts e.to_s.strip + exit 1 +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/lecture_5/homework/spec/requests/buildings.rb b/lecture_5/homework/spec/requests/buildings.rb new file mode 100644 index 00000000..090ba794 --- /dev/null +++ b/lecture_5/homework/spec/requests/buildings.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Buildings API', type: :request do + describe 'GET /buildings' do + it 'responds with 200' do + get '/buildings' + expect(response).to have_http_status(200) + end + end + + describe 'GET /buildings/:id' do + let(:building) { create(:building, name: building_name, granary: building_granary) } + let(:building_name) { 'default' } + let(:building_granary) { 100 } + + it 'responds with 200' do + get "/buildings/#{building.id}" + expect(response).to have_http_status(200) + end + + context 'destroy' do + before { building.destroy } + + it 'responds with 404' do + get "/buildings/#{building.id}" + expect(response).to have_http_status(404) + end + end + end +end diff --git a/lecture_5/homework/spec/services/sedge_report_spec.rb b/lecture_5/homework/spec/services/sedge_report_spec.rb new file mode 100644 index 00000000..8620ff8d --- /dev/null +++ b/lecture_5/homework/spec/services/sedge_report_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Reports::SiegeReport do + subject(:siege_report) { Reports::SiegeReport.new(building: building) } + + let(:building) { create(:building, name: building_name, granary: building_granary) } + let(:building_name) { 'default' } + + describe 'no warriors and granary' do + let(:building_granary) { 0 } + + it 'without granary and warriors returns 0' do + expect(siege_report.call).to eq(0) + end + end + + describe 'no warriors' do + let(:building_granary) { 11 } + + it 'without warriors returns 0' do + expect(siege_report.call).to eq(0) + end + end + + describe 'warriors and granary' do + + context 'one inflantry' do + let(:warrior1) { build(:samurai) } + let(:building_granary) { 11 } + + it 'should return 1' do + warrior1.building = building + warrior1.save + expect(siege_report.call).to eq(1) + end + end + + context 'one cavalery' do + let(:warrior1) { build(:hussar) } + let(:building_granary) { 12 } + + it 'should return 1' do + warrior1.building = building + warrior1.save + expect(siege_report.call).to eq(1) + end + end + + context 'both' do + let(:warrior1) { build(:hussar, name: 'default2') } + let(:warrior2) { build(:samurai) } + let(:building_granary) { 12 } + + it 'should return 1' do + warrior1.building = building + warrior1.save + warrior2.building = building + warrior2.save + expect(siege_report.call).to eq(0) + end + end + end +end diff --git a/lecture_5/homework/spec/spec_helper.rb b/lecture_5/homework/spec/spec_helper.rb new file mode 100644 index 00000000..79c3d407 --- /dev/null +++ b/lecture_5/homework/spec/spec_helper.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# require 'factory_bot' +# FactoryBot.find_definitions +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + + # The settings below are suggested to provide a good initial experience + # with RSpec, but feel free to customize to your heart's content. + # # This allows you to limit a spec run to individual examples or groups + # # you care about by tagging them with `:focus` metadata. When nothing + # # is tagged with `:focus`, all examples get run. RSpec also provides + # # aliases for `it`, `describe`, and `context` that include `:focus` + # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + # config.filter_run_when_matching :focus + # + # # Allows RSpec to persist some state between runs in order to support + # # the `--only-failures` and `--next-failure` CLI options. We recommend + # # you configure your source control system to ignore this file. + # config.example_status_persistence_file_path = "spec/examples.txt" + # + # # Limits the available syntax to the non-monkey patched syntax that is + # # recommended. For more details, see: + # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + # config.disable_monkey_patching! + # + # # Many RSpec users commonly either run the entire suite or an individual + # # file, and it's useful to allow more verbose output when running an + # # individual spec file. + # if config.files_to_run.one? + # # Use the documentation formatter for detailed output, + # # unless a formatter has already been configured + # # (e.g. via a command-line flag). + # config.default_formatter = "doc" + # end + # + # # Print the 10 slowest examples and example groups at the + # # end of the spec run, to help surface which specs are running + # # particularly slow. + # config.profile_examples = 10 + # + # # Run specs in random order to surface order dependencies. If you find an + # # order dependency and want to debug it, you can fix the order by providing + # # the seed, which is printed after each run. + # # --seed 1234 + # config.order = :random + # + # # Seed global randomization in this process using the `--seed` CLI option. + # # Setting this allows you to use `--seed` to deterministically reproduce + # # test failures related to randomization by passing the same `--seed` value + # # as the one that triggered the failure. + # Kernel.srand config.seed +end diff --git a/lecture_5/homework/spec/support/factory_bot.rb b/lecture_5/homework/spec/support/factory_bot.rb new file mode 100644 index 00000000..2e7665cc --- /dev/null +++ b/lecture_5/homework/spec/support/factory_bot.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods +end