justinp.io projects about contact


The Tradeline credit dashboard is a project I worked on for around a year. I led the development for a private business investor in developing this full-stack financial services application, using Ruby on Rails.


The frontend was an inherited design from the previous developer, and it took some considerable work to get it into a functional state. I made extensive use of the Datatables framework for returning results from the database in an asynchronous and efficient fashion. All filter operations are pushed down into the database layer as best possible

tl = Listing.accessible_by(current_ability).active
tl = tl.where(bank_id: listing_filter_params[:bank]) if listing_filter_params[:bank].present?
tl = tl.where(state_id: listing_filter_params[:state]) if listing_filter_params[:state].present?
tl = tl.where('credit_limit_cents >= ?', listing_filter_params[:min_credit_limit].to_money.fractional) if listing_filter_params[:min_credit_limit].present?
tl = tl.where('price_cents <= ?', listing_filter_params[:max_price].to_money.fractional) if listing_filter_params[:max_price].present?
tl = tl.where('? >= history', listing_filter_params[:age].to_i.years.ago) if listing_filter_params[:age].present?
tl = tl.where('active_slots >= ?', listing_filter_params[:slots].to_i) if listing_filter_params[:slots].present?
tl = tl.where(bpars) unless bpars.empty?
tl

The core four operations of the site were Deposits, Purchases, Withdrawals, and Refunds. Each of these had a discrete model, but were themselves attached polymorphically to a general LedgerEntry to decouple the concerns of affecting wallet balance and presenting transaction data.


On the frontend, users are given forms to fill out including AWS-S3 based file fields. Most complex form validations are done on the server side to increase overall responsiveness, but some basic ones are implemented in the view, as well as the Dropzone based uploader. Asynchronous image uploads done via the Shrine gem help significantly in speeding up form submission times.




The Administrate based backend provides the staff quick access to all the information they need to confirm and reject transactions, and confirm essential user details according to business logic.




Keeping track of which Tradelines were being listed in which month requires a "template" and "instance" pattern, with Tradelines generating Listings that last for a period of time before expiring on the noon of their statement date. Automatic workers handle promoting and demoting listings from tradelines who have been set to launch.



scope :ready_for_launch, -> { where(is_active: false).where('launch_date <= :date', date: Time.now) }
scope :expiring, -> { where(is_active: true).where('expiration_date <= :date', date: Time.now) }

Reference numbers for transactions were necessary for customer service operations, as well as validating deposit authenticity. Each Ledger Entry has its own reference number, as do listings and tradelines. These were created using the same concern code, and added on to all of the classes that needed them.


module Reference
  extend ActiveSupport::Concern

  included do
    before_validation :create_ref
    after_create :confirm_ref
  end

  def create_ref
    self.ref ||= gen_ref_code
  end

  def confirm_ref
    if self.id && self.ref
      rn = ReferenceNumber.create(ref: self.ref, reference_object: self)
    end
  end

  def gen_ref_code
    loop do
      token = SecureRandom.hex.first(8).tr('ef', 'xz').upcase
      break token unless ReferenceNumber.exists?(ref: token)
    end
  end
end

Since handling Secure data like SSNs and Drivers licenses is essential, two seperate image uploading models were created, one for public images such as bank logos and profile images, and a seperate one for SSN, Drivers Licenses, and Receipts. Using AWS-S3 Access Control Lists, single-use URLs are generated for sensitive documents.


module SecureAttachment
    extend ActiveSupport::Concern

    @@attachments = {}

    def self.attachments
        @@attachments
    end

    class_methods do
        def secure_attachment(atch, klass=self)
            @secured_attachments ||= {}
            @secured_attachments[atch] = klass

            @@attachments[klass] ||= []
            @@attachments[klass] << atch

            include SecureFileUploader::Attachment.new(atch)

            define_method("#{atch}?".to_sym) do
                self.method(atch).call.nil? ? false : true
            end
        end

        def secured_attachments
            @secured_attachments ||= {}
        end
    end
end