Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

TreasureMap README


TreasureMap is a single-page web app clone of the site Atlas Obscura, which hosts publicly sourced articles detailing odd and interesting places across the world.

View the TreasureMap wiki

Main Features:

Users can:

  • View articles, as well as create, update and delete them upon signing in
  • Search for articles by name, city or country


Explore TreasureMap for yourself!

Technologies Used

In this fullstack project, I employed the following technologies:

  • React
  • Redux
  • Javascript
  • JSX
  • HTML
  • CSS
  • Ruby
  • Ruby on Rails
  • SQL
  • JBuilder

Notable Features

Concurrent creation of articles, images and article edits

Via a relational database structure, my Article model utilizes its associations and the #accepts_nested_attributes_for ActiveRecord class method to concurrently create images and/or article edits when an article is created or edited via the article form. This consolidates three separate backend hits into one AJAX call and eliminates the need for an Images or ArticleEdits controller since these resources are always dependent on articles.

I streamlined this implementation via the following associations:

# article.rb

class Article < ApplicationRecord

has_many :edits,
  class_name: :ArticleEdit,
  foreign_key: :article_id

has_many :editors,
  through: :edits,
  source: :editor

has_many :images,
  class_name: :Image,
  foreign_key: :article_id,
  inverse_of: :article

accepts_nested_attributes_for :images, :edits


My ArticlesController allowed for nested image and articleEdit generation via its strong params (below):

# articles_controller.rb

class Api::ArticlesController < ApplicationController


def article_params
  params.require(:article).permit(:name, :description, :body, :lat, :lng,
    :city_id, :long_description, images_attributes: [:image], edits_attributes: [:editor_id])


The article form compiled article, image and edit information to send up as a single FormData bundle via its submit method:

// article_form.jsx



  let formData = new FormData();
  Object.keys(this.state.article).forEach(key => {
    formData.append(`article[${key}]`, this.state.article[key]);

  this.state.images.forEach(image => {
    formData.append("article[images_attributes[][image]]", image.imageFile);

  if (this.props.editorId){
    formData.append("article[edits_attributes][][editor_id]", this.props.editorId);

  //action is a prop which will submit an AJAX call to create or edit an article,
  depending on the form's intended use

Intelligent loading of top countries and cities by popularity

To more easily facilitate the connection of users and interesting places for them to visit, I created a dropdown on the site's navbar which highlights the top countries and cities featured on the site (using the heuristic of associated article counts). This feature provided two interesting challenges: (1) querying by associations to determine article count and (2) intelligently fetching shallow vs. deep country and city information to conserve client-side RAM.

(1) Querying by associations

I made the following ActiveRecord queries to determine the top countries and cities, respectively, by article count upon the app's initial load:

# country.rb

class Country < ApplicationRecord

def self.top_countries_by_article_count
  countries ='countries.*').joins(:cities).joins(:articles).group('').order('COUNT( DESC').limit(6)

# city.rb

class City < ApplicationRecord

def self.top_cities_by_article_count
  cities ='cities.*').joins(:articles).group('').order('COUNT( DESC').limit(12)

(2) Shallow vs. deep queries for country and city information

When displaying countries and cities for the navbar dropdown menu, all my redux store should hold is the country or city's :id and :name to optimize client-side storage. Only when a user clicks on one of these links and navigates to a country or city show page do I need to fetch a complete information set containing associated article, author, editor and image information.

For example, to determine when a country show page needs to bypass its render method and fire an AJAX request to fetch its full information from the database I created two slices of redux state nested under ui:

(1) countryDetailLoaded: this points to a boolean (defaults to false) and is passed to the component to conditionally return early from the initial render when the value is false. This turns to true after a component is loaded and then turns back to false in the componentWillUnmount lifecycle method.

(2) currentCountryDetailId: this is an array which holds the id of the current country show page (if the user is already on the component). If the array holds a value, the component will return early from its render method and re-fetch updated country information via its componentDidUpdate lifecycle method.

The below demo illustrates how the user can interact with the navbar dropdown (requiring a shallow information set for countries and cities) and click a link directed toward a country or city show page (requiring another AJAX call for the full page information).

map dropdown link

Future Direction

I am excited to continue improving the site! In the future I plan to:

  • Integrate city and country creation with the article form
  • Implement partial text search
  • Implement "Been There" and "Want to Visit" tags that users can assign to articles
  • Create a user profile page which documents the aforementioned tags and holds personalized maps of visited or wishlist places


An article forum for the world's most hidden places, inspired by Atlas Obscura






No releases published


No packages published