Search code examples
ruby-on-railsmongoidgeojson

What is the exact way of creating a model for GeoJson data in Rails API using Mongoid ORM?


I'm just curious how to handle nested json objects using mongoid, believing that it's possible. Here is an example GeoJson data from my frontend app:

{
    "type": "Feature",
    "properties": {
        "name": "A Cool Feature",
        "amenity": "A Cool Label",
        "popupContent": "This is a cool area.."
    },
    "geometry": {
        "type": "polygon",
        "coordinates": [
            {
                "lat": 41.47566020027821,
                "lng": 269.65942382812506
            },
            {
                "lat": 40.17047886718111,
                "lng": 266.81396484375006
            },
            {
                "lat": 38.41055825094609,
                "lng": 268.86840820312506
            },
            {
                "lat": 38.24680876017446,
                "lng": 270.91186523437506
            },
            {
                "lat": 40.871987756697415,
                "lng": 271.57104492187506
            }
        ]
    }
}

And here is my controller (very basic one)

class Api::V1::GeoDataController < ApplicationController

  # GET /geo_data
  def index
    @geo_data = GeoDatum.all
    render json: @geo_data
  end
  
  # POST /geo_data
  def create
    @geo_datum = GeoDatum.new(geo_datum_params)
    if @geo_datum.save
      render json: @geo_datum
    else
      render error: { error: 'Unable to create GeoDatum.' }, status: 400
    end    
  end
  
  private

  def geo_datum_params
    params.require(:geo_datum).permit(:type, :properties, :geometry)
  end

end

Models are here:

class GeoDatum
  include Mongoid::Document
  include Mongoid::Timestamps
  field :type, type: String
  field :properties, type: Property
  field :geometry, type: Geometry

  embeds_one :properties, class_name: "Property"
  embeds_one :geometry, class_name: "Geometry"
end

class Property
  include Mongoid::Document
  field :name, type: String
  field :amenity, type: String
  field :popupContent, type: String

  embedded_in :geo_datum, class_name: "GeoDatum"
end

class Geometry
  include Mongoid::Document
  field :type, type: String
  field :coordinates, type: Array

  embeds_many :coordinates, class_name: "LatLng"
  embedded_in :geo_datum, class_name: "GeoDatum"
end

class LatLng
  attr_reader :longitude, :latitude

  def initialize(lng, lat)
    @longitude = lng
    @latitude = lat
  end

  embedded_in :geometry, class_name: "Geometry"
end

Here is the console log for POST request:

Started POST "/api/v1/geo_data" for 192.168.1.209 at 2020-07-13 21:58:53 +0000
Processing by Api::V1::GeoDataController#create as */*
  Parameters: {"type"=>"Feature", "properties"=>{"name"=>"A Cool Feature", "amenity"=>"A Cool Label", "popupContent"=>"This is a cool area.."}, "geometry"=>{"type"=>"polygon", "coordinates"=>[{"lat"=>41.47566020027821, "lng"=>269.65942382812506}, {"lat"=>40.17047886718111, "lng"=>266.81396484375006}, {"lat"=>38.41055825094609, "lng"=>268.86840820312506}, {"lat"=>38.24680876017446, "lng"=>270.91186523437506}, {"lat"=>40.871987756697415, "lng"=>271.57104492187506}]}, "geo_datum"=>{"type"=>"Feature", "properties"=>{"name"=>"A Cool Feature", "amenity"=>"A Cool Label", "popupContent"=>"This is a cool area.."}, "geometry"=>{"type"=>"polygon", "coordinates"=>[{"lat"=>41.47566020027821, "lng"=>269.65942382812506}, {"lat"=>40.17047886718111, "lng"=>266.81396484375006}, {"lat"=>38.41055825094609, "lng"=>268.86840820312506}, {"lat"=>38.24680876017446, "lng"=>270.91186523437506}, {"lat"=>40.871987756697415, "lng"=>271.57104492187506}]}}}
Unpermitted parameters: :properties, :geometry
MONGODB | [11] 172.17.0.1:27017 #1 | leaflet.insert | STARTED | {"insert"=>"geo_data", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5f0cd91d44041302afa3ea2a'), "type"=>"Feature", "updated_at"=>2020-07-13 21:58:53.921377228 UTC, "created_at"=>2020-07-13 21:58:53.921377228 UTC}], "$db"=>"leaflet", "lsid"=>...
MONGODB | [11] 172.17.0.1:27017 | leaflet.insert | SUCCEEDED | 0.005s
Completed 200 OK in 14ms (Views: 0.7ms | MongoDB: 0.0ms | Allocations: 1098)

The point is that Unpermitted parameters: :properties, :geometry is happening, those attributes are not being fed at all. Any help would be appreciated.


Solution

  • if you want nested object(s) then you wrap it inside a hash or array… like this

    params.require(:foo).permit(:type, :properties => [:name, :amenity, :popUpContent], geometry: [:type, { coordinates: [:lat, :lng] }])

    see doc