Skip to content

Refactoring Orphan & Co. #162

@NikitaAvvakumov

Description

@NikitaAvvakumov

I ignored my own advice and started refactoring the Orphan model. It is very likely that these early efforts will have to either wait for a long time before being integrated into the OSRA code base, or be completely discarded and started anew once the churn subsides. However, having spent just a little time analyzing the situation, I think that the sooner we start thinking about the issues in our project’s architecture, the smoother any future refactoring will go. We might even start writing better code right away 😃

I am sharing this for two reasons: First, I do not claim that my opinions are universally correct and would appreciate input from the rest of the team. Second, the refactoring of the Orphan model alone has the potential to turn into a huge job, and the possibilities to split it across multiple PRs may be limited due to many dependencies (that alone is a good sign that refactoring is overdue). So, getting everyone involved in the process will hopefully make future code reviews less painful.

Extract Father from Orphan

I began with the very obviously needed task of extracting orphan_father into its own model. After initially directly copying all father-related attributes from Orphan, I replaced father_alive & father_is_martyr with enums status & martyr_status. I shared an article on enums a while back, and here’s the API link - they provide a simplified state machine-like functionality to Rails models and are, in many/most cases, a much better fit than boolean attributes. For instance, they automatically provide predicate and bang methods as well as scopes:

class Father < ActiveRecord::Base
  enum status: %i(dead alive)
  enum martyr_status: %i(not_martyr martyr)
end

father = Father.create(attr_hash)

father.alive! # => sets status to alive
father.alive? # => true
father.dead? # => false
father.status # => “alive”

Father.alive # => ActiveRecord_Relation of all Father instances with status “alive”

Enums

Using enums got me thinking about the many other places in the code base they can be used. Particularly, I wonder if we can eliminate all Status models and replace them with enums in their current associations. Not only would this have performance benefits (less db access, for example), it also makes sense from the architectural perspective (to me, at least): these objects are created once (seeded) and are immutable; they have no business logic of their own; they are, essentially, strings: “Active”, “Sponsored” etc.; association of an object with a particular status represents the object's state.

Importer

Once the Orphan & Father models are separated, the importer will break. To restore its functionality, changes will be needed in at least 2 places: OrphanImporter.to_orphan & collection_action :import within app/admin/pending_orphan_list.rb. Again, this is a good indicator that refactoring is called for.

I propose that creating and persisting valid model instances is the job of neither OrphanImporter nor PendingOrphanList but of a separate class. This new class would only report success or failure to the others, and it would be then up to the importer to decide whether to proceed with the import, and up to the PendingOrphanList to either call a “persistor” or destroy itself. This separation of concerns not only seems more logical to me, it will also allow for modification of only a single class if the Orphan model undergoes further changes in the future.

Pending models

Thinking about the importer, I could not explain to myself why PendingOrphanList & PendingOrphan are database models. Under no circumstances do we want them to be persisted (as in ‘long-term’). They come into existence during the import process and are supposed to disappear at its end, whether it is successful or not. PendingOrphanList provides the import UI at the moment, but even if we want ActiveAdmin to handle things, we may be able to move away from ActiveRecord. PendingOrphan has no UI and no logic of its own. Could it not be a non-db class? A hash of its instances could be held in memory during the import process and either converted into Orphan objects on success or discarded on failure.

Save the best for last

I replaced array join with string interpolation in Orphan#full_name. String interpolation is more efficient - my tests show that after the method gets called 1,000,000 times, we will have saved ~0.3-0.4s over the original implementation. No need for applause.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions