justinp.io projects about contact


ClairMarlo is the first of three linked web projects done for this client, professional composer Clair Marlo.

It is a portfolio website that allows her to show off her extensive and varied collection of project credits. She needed an upgrade to her existing website; it had a lot of content that had spiraled out into something disorganized and visually tiring, and the thematic elements were unfocused and hard to read. In short, it needed a redesign and a rebranding that would allow it to become organized and stay organized. There are 4 public subpages on the site, that serve the functions of homepage, list of credits, list of press quotations, and a contact form.


The homepage randomly samples some of the available project credits to populate itself with project bubbles. Each of these bubbles is a smart link to the project, and serve to illustrate the great magnitude of work Clair has done. Using flexbox and media-query CSS techniques, it automatically changes its size in response to screen, becoming an impressive grouped wall of content at any scale. It also contains biographical text, and a recent news blurb that can be changed by the client.

The Works page displays all of Clair's project credits, and allows you to sort by composition type. Clicking on one of them leads to a detail page that also displays notes from the artist. Only project credits designated for this site appear under it; Clair's other websites also access this resource and pull their data from it. Of course, the client can add / edit / delete entries from this list at will.


The Press page displays a slideshow of Clair's many favorable reviews, randomized and with a few colors. Since these are less essential than the project credits, they don't have individual pages to examine them— there's not more to know than the quote and the source.



The Contact page has a form that visitors can fill out to be put in touch with Clair Marlo. They must enter a First and Last name, and either a phone or email to return the message at. This form is hooked into backend worker network that filters and metes out contacts to keep from bogging Clair down with messages.


The Administrate based backend provides convenient and simple dashboards by which the client can edit the information. Project credits, frontpage news blurbs, and new press quotes can all be added, modified, and deleted by the client. Additionally, this single backend location serves the needs of the other upcoming ClairMarlo websites, storing project credits for them as well. This centralized backend allows the client a simple dropdown menu to change the website the project credit will appear on, and Rails scopes and SQL indexing are used to ensure speedy selection of the right active records.



Additionally to all of this, Contact attempts made through the Contact page form are stored by the App and converted into a bi-monthly email digest automatically created and sent by the system. A threshold level is also user-definable, beyond which the system immediately sends all outstanding contacts. In addition to sending the information in the body of the email, the app is able to create and include standard VCard contact cards as attachments, which can be one-click integrated with all common digital address books.



I employed several new-to-me programming patterns in designing this website, which was highly presentation focused. Using the "Decorator" pattern, I created draper classes which provide a location to put presentation code, seperate from both the view code and business logic. These classes take in instances of the models they decorate, and return a thin wrapper with the extra presentation-based methods. Other calls are delegated to the original object.

class ProjectBubbleDecorator < Draper::Decorator
  delegate_all

  def tiny_image
    object.image.tiny.url
  end

  def small_image
    object.image.small.url
  end

  def medium_image
    object.image.medium.url
  end

  def link
    object.url
  end

  def detail_container_classes
    join_classes('detail_bubble_img_wrapper', 'bubble_color_' + bubble_colors.sample.to_s)
  end

  def bubble_classes
    join_classes('project_bubble_inner', 'bubble_color_' + bubble_colors.sample.to_s)
  end
end

This allows highly laconic syntax for rendering objects with even complex presentations, as you can do most of the heavy lifting on the Ruby side instead of HTML/CSS

# In the home page view

<% @selected_works.each do |project| %>
<%= render 'project_bubble', project: project %>
<% end %>

<!-- In the project_bubble partial -->

<div class="project_bubble_div">
        <div class="<%= project.bubble_classes %>">
                <%= link_to image_tag(project.small_image, :alt => project.title, :class => 'project_bubble_img'), project.model, :class => 'project_bubble_link' %>
        </div>
</div>

Also of note is the use of the ActionMailer framework to send the client contact attempts. This employs a group of Worker classes which are called to perform quick and asynchronous actions related to these attempts, including Serializing the information to a VCard, pruning them once they're no longer needed, and sending regular and impromptu updates.

class VCardCleanupWorker
  def perform
    existing = Dir.entries(Rails.root.join('tmp', 'vcards')).select { |x| x =~ /(.*)\.vcard/ }
    outstanding = ContactAttempt.where(notified: false).to_a
    valids = outstanding.map(&:vcard_file_string)
    existing.each { |e| File.delete(Rails.root.join('tmp', 'vcards', e)) unless valids.include?(e)}
  end
end

class AllOutstandingWorker 
  def perform
    ContactMailer.all_outstanding.deliver if any_new?
  end
end

class CheckOutstandingWorker
  def perform
    ca_count = ContactAttempt.where(notified: false).count
    ca_last = ContactAttempt.where(notified: false).first

    if ca_count > ENV['NOTIFY_THRESHOLD'].to_i
      if ca_count == 1
        SingleContactWorker.perform_async(ca_last.id)
      else
        AllOutstandingWorker.perform_async
      end
    end
  end
end

Many different technologies were involved in setting this system up from scratch, some of which were new to me at the time. It is hosted on a DigitalOcean droplet VPS, running Ubuntu and Ruby 2.5.x. Serving the application is a Nginx server running Phusion Passenger, which will eventually support all three websites. The system runs a Redis server for caching and Sidekiq to handle asynchronous scheduled task execution, backed by good ol' Cron for the concrete tasks of automatically executing the code on a schedule.