{"componentChunkName":"component---src-templates-post-js","path":"/rails-api-for-triple-nested-resources","result":{"data":{"mdx":{"frontmatter":{"title":"Rails API for Triple Nested Resources","date":"08/21/2020"},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Rails API for Triple Nested Resources\",\n  \"slug\": \"rails-api-for-triple-nested-resources\",\n  \"date\": \"2020-08-21T00:00:00.000Z\",\n  \"image\": \"./images/rails-field.jpg\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"This post provides one way to handle triple nested resources with a Rails API using the Fast JSON API gem. It also touches on how to arrange data for HTTP POST requests from a JavaScript frontend. It is based on my experience building the Trip Planner app.\"), mdx(\"p\", null, \"Below is a screenshot of the app that demonstrates a triple nested relationship among the three resources: Trip, Day, and Place. At this moment, before a user saves data through a POST request to the backend, the DOM includes the following elements:\"), mdx(\"p\", null, mdx(\"img\", _extends({\n    parentName: \"p\"\n  }, {\n    \"src\": \"https://cdn-images-1.medium.com/max/3840/1*cZuRJgjuQX91HzI1w4cz7Q.png\",\n    \"alt\": \"Visual of Triple Nested Relationship\"\n  })), mdx(\"em\", {\n    parentName: \"p\"\n  }, \"Visual of Triple Nested Relationship\")), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"One trip (represented by the red box) with a map centered on the trip\\u2019s destination city;\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Several days (green boxes) representing the daily planners; and\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Each daily planner (green box) includes several places (blue boxes).\")), mdx(\"p\", null, \"This can be translated into the following Active Record associations:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"A Trip has many Days; A Trip has many Places through Days\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"A Day belongs to a Trip; A Day has many Places\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"A Place belongs to a Day\")), mdx(\"p\", null, \"With the structure laid out, I just needed to ensure all of the following aspects are covered for a full-stack implementation.\"), mdx(\"h3\", null, \"Backend\"), mdx(\"h4\", null, \"Rails API Setup\"), mdx(\"p\", null, \"Create the Rails backend with the api flag, which leads to some Rails default features and middleware being removed. They are mostly related to the browser. This also ensures that controllers will inherit from \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ActionController::API\"), \" rather than \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"ActionController::Base\"), \" and generators will skip generating views.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"rails new trip-planner-backend --api --database=postgresql\\n\")), mdx(\"p\", null, \"In the Gemfile, uncomment gem \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"'rack-cors'\"), \" and add gem \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"'fast_jsonapi'\"), \" before \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bundle\"), \" or \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"bundle install\"), \". Then, uncomment the following code:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-rb\"\n  }), \"# config/initializers/cors.rb\\n\\nRails.application.config.middleware.insert_before 0, Rack::Cors do\\n  allow do\\n    origins '*' # for development only, change to your origin in production\\n\\n    resource '*',\\n      headers: :any,\\n      methods: [:get, :post, :put, :patch, :delete, :options, :head]\\n  end\\nend\\n\")), mdx(\"h4\", null, \"Rails Resource Generators and Migrations\"), mdx(\"p\", null, \"The resource generators below will create the the corresponding migrations, models and controllers. They will not create views.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"rails g resource trip city lat:decimal lng:decimal\\nrails g resource day date:date trip:references\\nrails g resource place name place_id category day:references\\n\")), mdx(\"p\", null, \"The following points are worth some explanations:\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"You can use \\u201Cdate\\u201D as the name of an attribute\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"You can\\u2019t use \\u201Ctype\\u201D as the name of an attribute; a good alternative is \\u201Ccategory\\u201D\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"The \\u201Cplace_id\\u201D attribute for the place resource is of the string data type that saves the Google Map API\\u2019s Place IDs; it is different from the primary key, id, automatically generated by the db\")), mdx(\"h4\", null, \"Rails Models\"), mdx(\"p\", null, \"The associations among the three resources should be reflected in the models. To be prepared for nested params, the models should also include \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"accepts_nested_attributes_for\"), \", as follows:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-rb\"\n  }), \"# app/models/trip.rb\\n\\nclass Trip < ApplicationRecord\\n  has_many :days, dependent: :destroy\\n  has_many :places, through: :days\\n  accepts_nested_attributes_for :days, reject_if: :all_blank\\nend\\n\\n\\n# app/models/day.rb\\n\\nclass Day < ApplicationRecord\\n  belongs_to :trip\\n  has_many :places, dependent: :destroy\\n  accepts_nested_attributes_for :places, reject_if: :all_blank\\nend\\n\\n\\n# app/models/place.rb\\n\\nclass Place < ApplicationRecord\\n  belongs_to :day\\nend\\n\")), mdx(\"h4\", null, \"Fast JSON API Serializers\"), mdx(\"p\", null, \"With the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/Netflix/fast_jsonapi\"\n  }), \"Fast JSON API gem\"), \" bundled, I can use serializer generators to create serializers that customize the attributes to be rendered in JSON. They can be generated like so:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-bash\"\n  }), \"rails g serializer Trip\\nrails g serializer Day\\nrails g serializer Place\\n\")), mdx(\"p\", null, \"This will create a \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"serializers\"), \" folder within \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"/app\"), \", and inside, \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"trip_serializer.rb\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"day_serializer.rb\"), \", and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"place_serializer.rb\"), \".\"), mdx(\"p\", null, \"The desired attributes and relationships to be rendered depend on the needs of your project. Below is my example:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-rb\"\n  }), \"# app/serializers/trip_serializer.rb\\n\\nclass TripSerializer\\n  include FastJsonapi::ObjectSerializer\\n  attributes :city, :lat, :lng\\n  has_many :days\\n  has_many :places, through: :days\\nend\\n\\n\\n# app/serializers/day_serializer.rb\\n\\nclass DaySerializer\\n  include FastJsonapi::ObjectSerializer\\n  attributes :date\\n  has_many :places\\n  belongs_to :trip\\nend\\n\\n\\n# app/serializers/place_serializer.rb\\n\\nclass PlaceSerializer\\n  include FastJsonapi::ObjectSerializer\\n  attributes :name, :place_id, :category\\n  belongs_to :day\\nend\\n\")), mdx(\"h4\", null, \"Rails Controllers\"), mdx(\"p\", null, \"With the models prepared with \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"accepts_nested_attributes_for\"), \", I can send HTTP requests to the outer-most resource inclusive of params for the inner resources. In my case, the outer-most resource is Trip, and the create action demonstrates the nesting very well.\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-rb\"\n  }), \"# app/controllers/trips_controller.rb\\n\\nclass TripsController < ApplicationController\\n  \\n  ...\\n    \\n  def create\\n    trip = Trip.new(trip_params)\\n    if trip.save\\n      options = { include: %i[days places] }\\n      render json: TripSerializer.new([trip], options).serialized_json\\n    else\\n      render json: { error: 'could not be created' }\\n    end\\n  end\\n  \\n  ...\\n    \\n  private\\n\\n  def trip_params\\n    params.require(:trip).permit(:city,\\n                                 :lat,\\n                                 :lng,\\n                                 days_attributes: [:date,\\n                                                   places_attributes: %i[name\\n                                                                         place_id\\n                                                                         category]])\\n  end\\nend\\n\")), mdx(\"p\", null, \"Note the triple nesting in the strong params: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"days_attributes\"), \" inside the permit, and \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"places_attributes\"), \" inside \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"days_attributes\"), \". This ensures that one POST request, hitting the TripsController\\u2019s \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"create\"), \" action, can create all the data points that belong to the trip.\"), mdx(\"p\", null, \"This concludes the work in the backend. The following section explains the frontend work.\"), mdx(\"h3\", null, \"Frontend\"), mdx(\"h4\", null, \"POST Request for Create\"), mdx(\"p\", null, \"From the data on the DOM, I need to construct the body of the POST request that conforms to the strong params with triple nesting accepted by the backend controller. It is very likely that the function to create this object (body) involves two \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"map()\"), \" methods, like so:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"const createTripObj = () => {\\n  \\n  // map through all day boxes on the DOM to get the days_attributes params array\\n  const days_attributes = [...document.querySelectorAll('.daily')].map(dayBox => {\\n    const date = dayBox.dataset.date;\\n\\n    // map through the place items inside this day box to get the places_attributes params array\\n    const places_attributes = [...dayBox.querySelectorAll('.place-item')].map(placeItem => {\\n      const name = placeItem.querySelector('.place-name').innerText;\\n      const place_id = placeItem.dataset.placeId;\\n      const category = placeItem.dataset.type;\\n      return { name, place_id, category };\\n    })\\n\\n    return { date, places_attributes };\\n  })\\n\\n  const city = state.cityName; // global variable\\n  const lat = state.mapCenter.lat(); // global variable\\n  const lng = state.mapCenter.lng(); // global variable\\n\\n  // return a structure same as the triple nested params\\n  return {\\n    trip: { city, lat, lng, days_attributes }\\n  };\\n}\\n\")), mdx(\"p\", null, \"If the above code is unclear, please reference the screenshot at the beginning of this post for some visualization. Essentially, this function returns an object structured in the below fashion, for example:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"{\\n   \\\"trip\\\" : {\\n      \\\"city\\\" : \\\"Washington\\\",\\n      \\\"lat\\\" : \\\"8.9071923\\\",\\n      \\\"lng\\\" : \\\"-77.0368707\\\",\\n\\n      \\\"days_attributes\\\" : [\\n         {\\n            \\\"date\\\" : \\\"Fri, 21 Aug 2020 21:56:07 GMT\\\",\\n           \\n            \\\"places_attributes\\\" : [\\n                {\\n                    \\\"name\\\" : \\\"Eastern Market\\\",\\n                    \\\"place_id\\\" : \\\"ChIJY8iLSTK4t4kRODLIp7hlw1Q\\\",\\n                    \\\"category\\\" : \\\"see\\\"\\n                },\\n                {\\n                    \\\"name\\\" : \\\"National Mall\\\",\\n                    \\\"place_id\\\" : \\\"ChIJMT3_Wpu3t4kRQScGokyrCDo\\\",\\n                    \\\"category\\\" : \\\"see\\\"\\n                }\\n             ]\\n         }\\n      ]\\n   }\\n}\\n\")), mdx(\"p\", null, \"With the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"tripObj\"), \" structured exactly like the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"trip_params\"), \" in the Rails TripController, inclusive of day and place information, I can send the data through a fetch request, like so:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-javascript\"\n  }), \"newTrip = tripObj => {\\n  const configObj = {\\n    method: \\\"POST\\\",\\n    headers: {\\n      \\\"Content-Type\\\": \\\"application/json\\\",   \\n      \\\"Accepts\\\": \\\"application/json\\\"\\n    },\\n    body: JSON.stringify(tripObj)\\n  }\\n\\n  fetch('http://localhost:3000/trips', configObj)  // use real server URL\\n    .then(res => res.json())\\n    .then(json => parseAndAddElement(json)); // parse response for rendering\\n}\\n\")), mdx(\"h3\", null, \"Conclusion\"), mdx(\"p\", null, \"Based on my experience, one of the most important skills for a full-stack implementation is the ability to traverse through data, no matter how it\\u2019s presented, for example, on the DOM or in JSON. This fundamental skill allows me to arrange and manipulate data with whichever programming language I am required to use. The triple nested resource written about in this post is just one of the easier examples. As I go through the software engineering program with the Flatiron School, I will continue to build this skill to be comfortable with different complex data structures.\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"slug":"rails-api-for-triple-nested-resources"}},"staticQueryHashes":["1063564771","2248308066","2468095761"]}