Skip to content
This repository has been archived by the owner on Oct 19, 2018. It is now read-only.

5.1 Squad CRUD refactor (after)

Barrie Hadfield edited this page Jan 5, 2017 · 1 revision

First refactor - using IWA architecture

  • We can get rid of the Controller, Representer, API and API tests
  • Concept CRUD code will be refactored to be an Hyperloop Operation
  • Concept test code will be refactored

We have to think about caching...

module Components
  module Squad

    class Admin < React::Component::Base

      # following added to remove warning messages
      param :route
      param :history
      param :location
      param :routeParams
      param :routes
      param :params

      define_state show_save: false
      define_state errors: []
      define_state show_modal: false
      define_state show_save: false

      before_mount do
        @mandate = ""
        @name = ""
        @leader_id = 0
        @tribe_id = 0
      end

      before_mount do
        state.squads! ReactiveRecord::Squad.all
        UIHelpers::UIHelper.clear_alert
      end

      def render
        div.container {
          if state.squads.nil? || state.squads.loading? # this is not working right yet
            br {}
            Shared::Spinner()
          else
            div.row {
              h2.text_info {
                i.fa.fa_th {}
                " Squads ".span
                small { Shared::Store.current_period[:name] } if Shared::Store.current_period
              }
              div.row {
                  div.panel.panel_default {
                    div.panel_body {
                      div.col_md_12 {
                        br
                        UIHelpers::AlertMessage()
                        modal_render
                        Bs.Button(bsStyle: :primary) { "New Squad" }.on(:click) do
                          reset_form
                          state.show_modal! true
                        end
                        table_render
                      }
                    }
                  }
              }
            }
          end
        }
      end

      def table_render
        div.row {
          div.col_md_12 {
            br {}
            table(class: "table table-hover") {
              thead {
                tr {
                  td.text_muted.small(width: '20%') { "SQUAD" }
                  td.text_muted.small(width: '20%') { "TRIBE" }
                  td.text_muted.small(width: '20%') { "LEADER" }
                  td.text_muted.small(width: '40%') { "MANDATE" }
                }
              }
              tbody {
                state.squads.each do |squad|
                  table_row squad
                end
              }
            }
          }
        }
      end

      def table_row squad
        tr {
            td(width: '20%') { span.link {
              squad.name }.on(:click) do
                set_form squad
                state.show_modal! true
              end
            }
            td(width: '20%') { squad.tribe.name }
            td(width: '20%') { squad.leader.full_name }
            td(width: '60%') { squad.mandate }
        }
      end

      def close
        state.show_modal! false
      end

      def reset_form
        @squad_id = 0
        @delete_disabled = true
        @name = ""
        @mandate = ""
        @leader_id = 0
        @tribe_id = 0
        state.show_save! false
        state.show_delete_confirm! false
        state.errors! []
        state.show_spinner! false
      end

      def set_form squad
        @squad_id = squad.id
        @delete_disabled = squad.can_delete? ? false : true
        @name = squad.name
        @mandate = squad.mandate
        @leader_id = squad.leader.id
        @tribe_id = squad.tribe.id
        state.show_delete_confirm! false
        state.errors! []
        state.show_spinner! false
      end

      def delete
        HTTP.delete("/api/v3/squad/#{@squad_id}.json") do |response|
          if response.ok?
            # Shared::Store.get_squads
            state.show_modal! false
            UIHelpers::UIHelper.set_alert @name, "has been deleted"
          else
            puts response
            alert "Unable to delete Squad"
          end
        end
      end

      def save
        data = {squad: {name: @name,
          mandate: @mandate, leader_id: @leader_id, tribe_id: @tribe_id}
        }
        if @squad_id == 0
          HTTP.post("/api/v3/squad.json", payload: data) do |response|
            if response.ok?
              state.show_modal! false
              UIHelpers::UIHelper.set_alert @name, "has been created"
            else
              alert "Unable to create Squad"
            end
          end
        else
          HTTP.patch("/api/v3/squad/#{@squad_id}.json", payload: data) do |response|
            if response.ok?
              # Shared::Store.get_squads
              state.show_modal! false
              UIHelpers::UIHelper.set_alert @name, "has been updated"
            else
              alert "Unable to update Squad"
            end
          end
        end
      end

      def save_modal
        state.errors! []
        name_validation
        mandate_validation
        leader_validation
        tribe_validation
        unless state.errors.any?
          state.show_spinner! true
          state.errors! []
          save
        end
      end

      def name_validation
        case
        when @name.length < 5
          state.errors! << "Name must be more than 5 chars"
        when @name.length > 30
          state.errors! << "Name must be less than 30 chars"
        end
      end

      def mandate_validation
        case
        when @mandate.length < 10
          state.errors! << "Mandate must be more than 10 chars"
        when @mandate.length > 100
          state.errors! << "Mandate must be less than 100 chars"
        end
      end

      def leader_validation
        case
        when @leader_id.to_i == 0 || !@leader_id
          state.errors! << "Please select a leader"
        end
      end

      def tribe_validation
        case
        when @tribe_id.to_i == 0 || !@tribe_id
          state.errors! << "Please select a Tribe"
        end
      end

      def modal_render
        Bs.Modal(show: state.show_modal, backdrop: 'static', onHide: lambda { close }) {
          Bs.ModalHeader {
            h4 { "Squad" }
          }
          Bs.ModalBody {
            Bs.FormGroup {
              Bs.ControlLabel { "* Tribe" }
              Bs.FormControl(componentClass: "select", placeholder: "select", defaultValue: @tribe_id) {
                option(value: 0) {"select"}
                ReactiveRecord::Tribe.all.each do |tribe|
                  option(value: tribe.id) { tribe.name }
                end
              }.on(:change) do |e|
                @tribe_id = e.target.value
              end
            }
            Bs.FormGroup {
              Bs.ControlLabel { "* Name" }
              Bs.FormControl.Feedback(defaultValue: @name ,type: :text, placeholder: "(5 - 30 chars)").on(:change) do |e|
                @name = e.target.value
              end
            }
            Bs.FormGroup {
              Bs.ControlLabel { "* Mandate" }
              Bs.FormControl(componentClass: :textarea, defaultValue: @mandate,
                placeholder: "What is the main purpose or mandate of this Squad? (5 - 200 chars)"
              ).on(:change) do |e|
                @mandate = e.target.value
              end
            }
            Bs.FormGroup {
              Bs.ControlLabel { "* Leader" }
              Bs.FormControl(componentClass: "select", placeholder: "select", defaultValue: @leader_id) {
                option(value: 0) {"select"}
                ReactiveRecord::Member.all.each do |member|
                  option(value: member.id) { member.full_name }
                end
              }.on(:change) do |e|
                @leader_id = e.target.value
              end
            }
            Bs.ModalFooter {
              span.pull_right {
                Bs.ButtonToolbar {
                  if !state.show_delete_confirm
                    if @squad_id != 0
                      Bs.Button(bsStyle: 'danger', class: 'danger-outline', disabled: @delete_disabled ? true : false) { 'Delete this Squad' }.on(:click) {
                        state.show_delete_confirm! true
                      }
                    end
                    Bs.Button(bsStyle: 'success') { 'Save' }.on(:click) { save_modal }
                  else
                    Bs.Button(bsStyle: :danger) { "Confirm Delete"}. on(:click) do
                      delete
                    end
                  end
                  Bs.Button { 'Cancel' }.on(:click) { close }
                }
              }
              if state.errors.any?
                br
                br
                div(class: "alert alert-danger") {
                  state.errors.each do |error|
                    para { "#{error}" }
                  end
                }
              end
              if state.show_spinner
                br
                br
                br
                Shared::SpinnerSpan()
                span { " working"}
              end
            }
          }
        }
      end
    end
  end
end

Then the Squad CRUD concept (operation):

class Squad < ActiveRecord::Base

  def can_delete?
    (objective_count == 0 && key_result_count == 0 && score_count == 0) ? true : false
    # false
  end

  class Create < Trailblazer::Operation
    include Model
    model Squad, :create
    contract do
      property :name
      property :mandate
      property :tribe_id
      property :leader_id

      validates :tribe_id, presence: true
      validate :valid_tribe?
      validates :leader_id, presence: true
      validate :valid_leader?
      validates :name, presence: true
      validates :name, length: {in: 5..30}
      validates :mandate, presence: true
      validates :mandate, length: {in: 5..200}

      def valid_tribe?
        errors.add("tribe_id", "not valid") unless Tribe.find_by_id(tribe_id)
      end

      def valid_leader?
        errors.add("leader_id", "not valid") unless Member.find_by_id(leader_id)
      end

    end
    def process(params)
      validate(params[:squad]) do |f|
        f.save
        Rails.cache.delete_matched("count/squads")
      end
    end
  end

  class Update < Create
    action :update
  end

  class Show < Trailblazer::Operation
    include Model
    model ::Squad, :find

    def process(params)
    end
  end


  class Index < Trailblazer::Operation
    include Collection

    def process(params)
    end

    def model!(params)
      # Squad.includes(:leader, :objectives, :key_results).order(:name)
      Squad.order(:name)
    end
  end

  class Delete < Trailblazer::Operation
    include Model
    model Squad, :find
    def process(params)
      if model.can_delete?
        model.destroy
        Rails.cache.delete_matched("count/squads")
      else
        return invalid!
      end
    end
  end

end

Then the model:

class Squad < ActiveRecord::Base

  belongs_to :leader, class_name: :Member
  belongs_to :tribe
  belongs_to :home_page, class_name: :Page

  has_many :objectives, -> { where is_enabled: true }, as: :owner
  has_many :squad_members
  has_many :members, through: :squad_members
  has_many :key_results, through: :objectives
  has_many :scores, through: :key_results
  has_many :stars, through: :scores

  default_scope -> { where(period_id: Period.current.id) }

  # pass 0 for all tribes
  scope :for_tribe, (lambda do |id|
      where(tribe_id: id) if id != 0
    end)
  scope :for_period, (lambda do |period_id|
      unscoped.where(period_id: period_id)
    end)


  def last_score
    self.scores.first
  end

  def last_scored_at
    scores.first.created_at if scores.any?
  end

  def score_count
    # tested ok
    Rails.cache.fetch("count/scores/squad/#{self.id}") do
      self.scores.count
    end
  end

  def objective_count
    # tested ok
    Rails.cache.fetch("count/objectives/squad/#{self.id}") do
      objectives.count
    end
  end

  def key_result_count
    #  tested ok
    Rails.cache.fetch("count/key_results/squad/#{self.id}") do
      key_results.count
    end
  end

  def parent
    tribe
  end

  def children
    nil
  end

  def set_period_on_create
    self.period_id = Period.current.id
    return true
  end

  def possible_parent_objectives
    tribe.objectives
  end

  def name
    db_name = read_attribute(:name)
    if db_name && !db_name.downcase.include?("squad") && !db_name.downcase.include?("chapter")
      db_name += " Squad"
    end
    db_name
  end

end

Then the Squad concept tests:

require 'test_helper'

class SquadCrudTest < MiniTest::Spec
  describe "Squad CRUD" do
    before do
      Rails.cache.delete_matched("current/company")
      Rails.cache.delete_matched("count")
      period = Period::Create.(period: {name: "Period valid 1", start_date: Date.today, end_date: Date.tomorrow}).model
      Company::Create.(company: {name: "Test company 2"})
      @member = Member::Create.(member: {first_name: "John", last_name: "Smith",
        email: "john@smith.com", password: 'AComplicated88test'}).model
      Period::SetAsCurrentPeriod.run(id: period.id)
      Period.count.must_equal 1
      Company.count.must_equal 1
      Member.count.must_equal 1
      @tribe = Tribe::Create.(tribe: {name: "Test tribe",
        mandate: "A valid mandate",
        leader_id: @member.id
      }).model
      @tribe.persisted?.must_equal true
      @tribe.name.must_equal "Test tribe"
      @company_objective = Objective::Create.(objective: {name: "Test objective", note: "A valid note",
        owner_type: "Company", owner_id: Company.current.id
       }).model
      @company_objective.persisted?.must_equal true
    end

    describe "Create" do
      it "persists valid" do
        squad = Squad::Create.(squad: {name: "Test squad",
          mandate: "A valid mandate", tribe_id: @tribe.id,
          leader_id: @member.id
        }).model
        squad.persisted?.must_equal true
        squad.name.must_equal "Test squad"
      end
      it "invalid as too short" do
        res, op = Squad::Create.run(squad: {name: "T", mandate: "A valid mandate", tribe_id: @tribe.id})
        res.must_equal false
        op.model.persisted?.must_equal false
      end
    end

    describe "Update" do
      it "persists valid after update" do
        squad = Squad::Create.(squad: {name: "A name which is long enough",
          mandate: "and a mandate", tribe_id: @tribe.id,
          leader_id: @member.id
        }).model
        squad.persisted?.must_equal true
        Squad::Update.(
          id:     squad.id,
          squad: {name: "A brand new name"}).model
        squad.reload
        squad.name.must_equal "A brand new name Squad"
      end
    end

   describe "Delete" do
     it "ok to delete" do
       squad = Squad::Create.(squad: {name: "A name which is long enough",
         mandate: "and a mandate", tribe_id: @tribe.id,
         leader_id: @member.id
        }).model
       squad.persisted?.must_equal true
       res, op = Squad::Delete.run(squad)
       res.must_equal true
       op.model.persisted?.must_equal false
     end
      it "must fail to delete Tribe if it has Squads" do
        squad = Squad::Create.(squad: {name: "Test tribe",
          mandate: "A valid mandate", tribe_id: @tribe.id,
          leader_id: @member.id}
        ).model
        squad.persisted?.must_equal true
        res, op = Tribe::Delete.run(@tribe)
        res.must_equal false
        op.model.persisted?.must_equal true
      end
     end

     describe "Correct counts from cache" do
       it "has correct Objective count" do
         squad = Squad::Create.(squad: {name: "A name which is long enough",
           mandate: "and a mandate", tribe_id: @tribe.id,
           leader_id: @member.id
          }).model
         squad.persisted?.must_equal true
         squad.objective_count.must_equal 0
         squad_objective = Objective::Create.(objective: {name: "Test objective for squad", note: "A valid note",
           owner_type: "Squad", owner_id: squad.id, parent_id: @company_objective.id
          }).model
         squad_objective.persisted?.must_equal true
         squad.objective_count.must_equal 1
         squad_objective2 = Objective::Create.(objective: {name: "Test objective 2 for squad", note: "A valid note",
           owner_type: "Squad", owner_id: squad.id, parent_id: @company_objective.id
          }).model
         squad_objective2.persisted?.must_equal true
         squad.objective_count.must_equal 2
       end
       it "has correct KeyResult count" do
         squad = Squad::Create.(squad: {name: "A name which is long enough",
           mandate: "and a mandate", tribe_id: @tribe.id,
           leader_id: @member.id
          }).model
         squad.persisted?.must_equal true
         squad_objective = Objective::Create.(objective: {name: "Test objective for squad", note: "A valid note",
           owner_type: "Squad", owner_id: squad.id, parent_id: @company_objective.id
          }).model
         squad_objective.persisted?.must_equal true
         squad.objective_count.must_equal 1
         squad.key_result_count.must_equal 0
         key_result = KeyResult::Create.(key_result: {name: "Test result", note: "A valid note",
           objective_id: squad_objective.id
          }).model
         key_result.persisted?.must_equal true
         squad.key_result_count.must_equal 1
       end
       it "has correct Score count" do
         squad = Squad::Create.(squad: {name: "A name which is long enough",
           mandate: "and a mandate", tribe_id: @tribe.id,
           leader_id: @member.id
          }).model
         squad.persisted?.must_equal true
         squad_objective = Objective::Create.(objective: {name: "Test objective for squad", note: "A valid note",
           owner_type: "Squad", owner_id: squad.id, parent_id: @company_objective.id
          }).model
         squad_objective.persisted?.must_equal true
         squad.objective_count.must_equal 1
         squad.key_result_count.must_equal 0
         key_result = KeyResult::Create.(key_result: {name: "Test result", note: "A valid note",
           objective_id: squad_objective.id
          }).model
         key_result.persisted?.must_equal true
         squad.key_result_count.must_equal 1
         squad.score_count.must_equal 0
         score = Score::Create.(score: {note: "Test note", key_result_id: key_result.id,
           achievement: 1, confidence: 1, created_by_id: @member.id}).model
         score.persisted?.must_equal true
         squad.score_count.must_equal 1
         score2 = Score::Create.(score: {note: "Test note2", key_result_id: key_result.id,
           achievement: 1, confidence: 1, created_by_id: @member.id}).model
         score2.persisted?.must_equal true
         squad.score_count.must_equal 2
       end
     end
   end

end
Clone this wiki locally