Skip to content

Commit

Permalink
Remove OpenStructs (#4882)
Browse files Browse the repository at this point in the history
  • Loading branch information
coalest authored Jan 10, 2025
1 parent 411d5ac commit 6e6adfa
Show file tree
Hide file tree
Showing 17 changed files with 87 additions and 45 deletions.
2 changes: 1 addition & 1 deletion app/controllers/items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def create
result = create.call

if result.success?
redirect_to items_path, notice: "#{result.item.name} added!"
redirect_to items_path, notice: "#{result.value.name} added!"
else
@base_items = BaseItem.without_kit.alphabetized
# Define a @item to be used in the `new` action to be rendered with
Expand Down
8 changes: 4 additions & 4 deletions app/models/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ class Event < ApplicationRecord
end
after_create :validate_inventory

# @return [Array<OpenStruct>]
# @return [Array<Option>]
def self.types_for_select
descendants.map { |klass|
OpenStruct.new(name: klass.name.sub("Event", "").titleize, value: klass.name)
}.sort_by(&:name)
descendants.map do |klass|
Option.new(name: klass.name.sub("Event", "").titleize, id: klass.name)
end.sort_by(&:name)
end

# Returns the most recent "usable" snapshot. A snapshot is unusable if there is another event
Expand Down
3 changes: 3 additions & 0 deletions app/models/option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PORO used to create select options in views
class Option < Data.define(:id, :name)
end
12 changes: 11 additions & 1 deletion app/models/purchase.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ class Purchase < ApplicationRecord
validates :amount_spent_in_cents, numericality: { greater_than: 0 }
validate :total_equal_to_all_categories

SummaryByDates = Data.define(
:amount_spent,
:recent_purchases,
:period_supplies,
:diapers,
:adult_incontinence,
:other,
:total_items
)

def storage_view
storage_location.nil? ? "N/A" : storage_location.name
end
Expand All @@ -84,7 +94,7 @@ def remove(item)
def self.organization_summary_by_dates(organization, date_range)
purchases = where(organization: organization).during(date_range)

OpenStruct.new(
SummaryByDates.new(
amount_spent: purchases.sum(:amount_spent_in_cents),
recent_purchases: purchases.recent.includes(:vendor),
period_supplies: purchases.sum(:amount_spent_on_period_supplies_cents),
Expand Down
9 changes: 9 additions & 0 deletions app/models/result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Immutable result object for use in services
class Result < Data.define(:value, :error)
# Set default attribute values
def initialize(value: nil, error: nil)
super
end

def success? = error.nil?
end
3 changes: 2 additions & 1 deletion app/models/storage_location.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,14 @@ class StorageLocation < ApplicationRecord

# @param organization [Organization]
# @param inventory [View::Inventory]
# @return [Array<Option>]
def self.items_inventoried(organization, inventory = nil)
inventory ||= View::Inventory.new(organization.id)
inventory
.all_items
.uniq(&:item_id)
.sort_by(&:name)
.map { |i| OpenStruct.new(name: i.name, id: i.item_id) }
.map { |i| Option.new(name: i.name, id: i.item_id) }
end

# @return [Array<Item>]
Expand Down
6 changes: 4 additions & 2 deletions app/queries/low_inventory_query.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class LowInventoryQuery
LowInventoryItem = Data.define(:id, :name, :on_hand_minimum_quantity, :on_hand_recommended_quantity, :total_quantity)

def self.call(organization)
inventory = View::Inventory.new(organization.id)
items = inventory.all_items.uniq(&:item_id)
Expand All @@ -7,7 +9,7 @@ def self.call(organization)
items.each do |item|
quantity = inventory.quantity_for(item_id: item.id)
if quantity < item.on_hand_minimum_quantity.to_i || quantity < item.on_hand_recommended_quantity.to_i
low_inventory_items.push(OpenStruct.new(
low_inventory_items.push(LowInventoryItem.new(
id: item.id,
name: item.name,
on_hand_minimum_quantity: item.on_hand_minimum_quantity,
Expand All @@ -17,6 +19,6 @@ def self.call(organization)
end
end

low_inventory_items.sort_by { |item| item[:name] }
low_inventory_items.sort_by { |item| item.name }
end
end
4 changes: 2 additions & 2 deletions app/services/item_create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ def call
new_item.sync_request_units!(@request_unit_ids)
end

OpenStruct.new(success?: true, item: new_item)
Result.new(value: new_item)
rescue StandardError => e
OpenStruct.new(success?: false, error: e)
Result.new(error: e)
end

private
Expand Down
4 changes: 2 additions & 2 deletions app/services/transfer_destroy_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def call
transfer.destroy!
end

OpenStruct.new(success?: true)
Result.new
rescue StandardError => e
OpenStruct.new(success?: false, error: e)
Result.new(error: e)
end

private
Expand Down
12 changes: 6 additions & 6 deletions app/views/dashboard/_low_inventory_report.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@
<tbody>
<% @low_inventory_report.each do |inventory_item| %>
<tr>
<td><%= inventory_item["name"] %></td>
<td><%= inventory_item.name %></td>
<td>
<% if inventory_item["total_quantity"] < inventory_item["on_hand_minimum_quantity"] %>
<strong class="text-red" title="Below minimum quantity"><%= inventory_item["total_quantity"] %></strong>
<% if inventory_item.total_quantity < inventory_item.on_hand_minimum_quantity %>
<strong class="text-red" title="Below minimum quantity"><%= inventory_item.total_quantity %></strong>
<% else %>
<%= inventory_item["total_quantity"] %>
<%= inventory_item.total_quantity %>
<% end %>
</td>
<td><%= inventory_item["on_hand_minimum_quantity"] %></td>
<td><%= inventory_item["on_hand_recommended_quantity"] %></td>
<td><%= inventory_item.on_hand_minimum_quantity %></td>
<td><%= inventory_item.on_hand_recommended_quantity %></td>
</tr>
<% end %>
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion app/views/events/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<%= render partial: "shared/date_range_picker", locals: {css_class: "form-control"} %>
</div>
<div class="form-group col-lg-3 col-md-3 col-sm-6 col-xs-12">
<%= filter_select(scope: :by_type, collection: Event.types_for_select, key: :value, value: :name,
<%= filter_select(scope: :by_type, collection: Event.types_for_select, key: :id, value: :name,
selected: params.dig(:filters, :by_type)) %>
</div>
<div class="form-group col-lg-3 col-md-3 col-sm-6 col-xs-12">
Expand Down
19 changes: 19 additions & 0 deletions spec/models/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@
RSpec.describe Event, type: :model do
let(:organization) { create(:organization) }

describe "::types_for_select" do
let(:a_event) { Class.new { def self.name = "AEvent" } }
let(:z_event) { Class.new { def self.name = "ZEvent" } }

before do
allow(described_class).to receive(:descendants).and_return([z_event, a_event])
end

it "returns an array of ids and names" do
results = described_class.types_for_select
ids = results.map(&:id)
names = results.map(&:name)

expect(results.size).to eq(2)
expect(ids).to eq(["AEvent", "ZEvent"])
expect(names).to eq(["A", "Z"])
end
end

describe "#most_recent_snapshot" do
let(:eventable) { FactoryBot.create(:distribution, organization_id: organization.id) }
let(:data) { EventTypes::Inventory.new(storage_locations: {}, organization_id: organization.id) }
Expand Down
8 changes: 4 additions & 4 deletions spec/requests/transfers_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@
end

context 'when the transfer destroy service was successful' do
let(:fake_success_struct) { OpenStruct.new(success?: true) }
let(:successful_result) { Result.new }

before do
allow(fake_destroy_service).to receive(:call).and_return(fake_success_struct)
allow(fake_destroy_service).to receive(:call).and_return(successful_result)
subject
end

Expand All @@ -115,11 +115,11 @@
end

context 'when the transfer destroy service was not successful' do
let(:fake_error_struct) { OpenStruct.new(success?: false, error: fake_error) }
let(:failing_result) { Result.new(error: fake_error) }
let(:fake_error) { StandardError.new('fake-error-msg') }

before do
allow(fake_destroy_service).to receive(:call).and_return(fake_error_struct)
allow(fake_destroy_service).to receive(:call).and_return(failing_result)
subject
end

Expand Down
14 changes: 7 additions & 7 deletions spec/services/item_create_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
end

context 'when there are no issues' do
it 'should return an OpenStruct with success? set to true and the item' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with success? returning true and the item' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(true)
expect(subject.item).to eq(fake_organization_item)
expect(subject.value).to eq(fake_organization_item)
end

it 'should execute the expected methods' do
Expand All @@ -48,8 +48,8 @@
allow(Organization).to receive(:find).with(organization_id).and_raise(ActiveRecord::RecordNotFound)
end

it 'should return a OpenStruct with an ActiveRecord::RecordNotFound error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with an ActiveRecord::RecordNotFound error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error).to be_a_kind_of(ActiveRecord::RecordNotFound)
end
Expand All @@ -62,8 +62,8 @@
allow(fake_organization_item).to receive(:save!).and_raise(fake_error)
end

it 'should return a OpenStruct with the raised error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with the raised error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error).to eq(fake_error)
end
Expand Down
4 changes: 2 additions & 2 deletions spec/services/kit_create_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@
end

context 'but the ItemCreationService is unsuccesful' do
let(:fake_error_struct) { OpenStruct.new(success?: false, error: error) }
let(:failing_result) { Result.new(error: error) }
let(:error) { Faker::Name.name }

before do
allow_any_instance_of(ItemCreateService).to receive(:call).and_return(fake_error_struct)
allow_any_instance_of(ItemCreateService).to receive(:call).and_return(failing_result)
end

it 'should not create the Kit' do
Expand Down
20 changes: 10 additions & 10 deletions spec/services/transfer_destroy_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
end

context 'when there are no issues' do
it 'should return an OpenStruct with success? set to true' do
it 'should return a result object with success? returning true' do
expect { subject }.to change { TransferDestroyEvent.count }.by(1)
expect(subject).to be_a_kind_of(OpenStruct)
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(true)
end

Expand All @@ -54,8 +54,8 @@
allow(Transfer).to receive(:find).with(transfer_id).and_raise(ActiveRecord::RecordNotFound)
end

it 'should return a OpenStruct with an ActiveRecord::RecordNotFound error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with an ActiveRecord::RecordNotFound error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error).to be_a_kind_of(ActiveRecord::RecordNotFound)
end
Expand All @@ -66,8 +66,8 @@
allow(TransferDestroyEvent).to receive(:publish).and_raise('OH NOES')
end

it 'should return a OpenStruct with the raised error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with the raised error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error.message).to eq('OH NOES')
end
Expand All @@ -79,8 +79,8 @@
allow(transfer).to receive(:destroy!).and_raise(fake_error)
end

it 'should return a OpenStruct with the raised error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return a result object with the raised error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error).to eq(fake_error)
end
Expand All @@ -92,8 +92,8 @@
allow(Audit).to receive(:finalized_since?).and_return(true)
end

it 'should return an OpenStruct with the raised error' do
expect(subject).to be_a_kind_of(OpenStruct)
it 'should return an result object with the raised error' do
expect(subject).to be_a_kind_of(Result)
expect(subject.success?).to eq(false)
expect(subject.error).to be_a_kind_of(StandardError)
end
Expand Down
2 changes: 0 additions & 2 deletions spec/system/dashboard_system_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'ostruct'

RSpec.describe "Dashboard", type: :system, js: true do
let(:organization) { create(:organization) }
let(:user) { create(:user, organization: organization) }
Expand Down

0 comments on commit 6e6adfa

Please sign in to comment.