This project provides an example of integrating the MongoDB with Rails using the
MongoDB Ruby Driver. To implement this, we will create a model class that
encapsulates all interaction with the MongoDB collection. The class methods will
implement the standard ActiveModel all() and find() methods in addition to
convenience methods to return the mongo_client and collection. Instances of the class
will represent a specific document and its properties. The save(), update(), and
destroy() instance methods are added to support further standard ActiveModel
behavior. Other properties required by the Rails scaffolding, which expects the model class
to be an ActiveModel instance, will also be added.
$ rails new zipsAdd the Mongoid gem to the Gemfile
gem 'mongoid', '~> 5.0.0'Run the bundler
$ bundleGenerate a mongo database configuration file.
$ rails g mongoid:config
(output:)
create config/mongoid.ymlWhile the defaults generated from the above command are fine, do make sure that the names
defined for development:clients:default:database and test:clients:default:database agree
with where you import the JSON in a later step.
$ cat config/mongoid.yml | grep -v \# | grep -v '^$'
development:
clients:
default:
database: zips_development
hosts:
- localhost:27017
options:
options:
test:
clients:
default:
database: zips_test
hosts:
- localhost:27017
options:
read:
mode: primary
max_pool_size: 1Next, we need to add some configurations to config/application.rb. This is used by stand-alone programs
like rails console to be able to load the Mongoid environment with fewer steps. This also configures
which ORM your scaffold commands use by default. Adding the mongoid gem had the impact of making it
the default ORM. The lines below show how we can set it back to ether ActiveRecord or Mongoid. I am
leaving it as ActiveRecord here so that we do not rock the boat too soon.
module Zips
class Application < Rails::Application
...
# bootstraps mongoid within applications -- like rails console
Mongoid.load!('./config/mongoid.yml')
# which default ORM are we using with scaffold
# add --orm none, mongoid, or active_record
# to rails generate cmd line to be specific
config.generators {|g| g.orm :active_record}
# config.generators {|g| g.orm :mongoid}
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
end
end$ rails sDownload the zips.json from the MongoDB web site. The
following shows an example of downloading it using curl. You may use a web browser. Place the
downloaded file in db/zips.json, relative to the root of your application. The db directory should
already exist.
curl http://media.mongodb.org/zips.json -o db/zips.jsonImport the zips.json into the zips collection in the zips_development database.
Note 1: The --db name must match the name defined in config/mongoid.yml
Note 2: If not yet running, start your MongoDB Server using mongod
$ mongoimport --drop --db zips_development --collection zips --file db/zips.json
2015-09-12T19:44:10.785-0400 connected to: localhost
2015-09-12T19:44:11.554-0400 imported 29353 documentsTo help familiarize you with the data we just imported, below is a represtantive document from
the zips.json data set
{
"_id" : "01007",
"city" : "BELCHERTOWN",
"loc" : [ -72.410953, 42.275103 ],
"pop" : 10579,
"state" : "MA"
}We will be manually creating an app/model/zip.rb class that initially can obtain a connection to
the MongoDB server and database. We will be evolving this class in the subsequent sections, so to
start, the zip.rb model will implement the following:
- a
mongo_clientclass method that returns the default database connection - a
collectionclass method that returns a reference to the zips collection.
These methods are consistent with methods implemented when using mongoid ORM.
class Zip
# convenience method for access to client in console
def self.mongo_client
Mongoid::Clients.default
end
# convenience method for access to zips collection
def self.collection
self.mongo_client['zips']
end
endIn the rails console, invoke the zip class methods to verify a connection can be made
and to ensure we are interacting with the intended database within Mongo. This database should
match with what was used as part of the mongoimport command and should contain the zip codes
imported from zips.json into our zips collection.
$ rails c
Loading development environment (Rails 4.2.3)
> Zip.mongo_client
=> #<Mongo::Client:0x34369420 cluster=localhost:27017>
> Zip.mongo_client[:zips]
=> #<Mongo::Collection:0x22032060 namespace=zips_development.zips>
> Zip.collection
=> #<Mongo::Collection:0x21998940 namespace=zips_development.zips>
> Zip.collection.find.count
DEBUG | zips_development.count | STARTED | {"count"=>"zips", "query"=>{}}
=> 29353 Now that we have a connection to the database and can access the collection, it is time to get started implementing methods to perform CRUD against our model.
Lets add a method that will return all documents from the zips collection.
We will be starting with a
projection
that only returns the fields of interest -- in our case: _id, city, state, and pop. Since we
started off earlier examples by eliminating location, I am choosing to show a projection that eliminates
that property from our output.
def self.all
collection.find
.projection({_id:true, city:true, state:true, pop:true})
end Within the rails console, verify we can use our implementation of the all() method that also
omits the location property.
> Zip.all.count
=> 29353
> Zip.all.first
=> {"_id"=>"99743", "city"=>"HEALY", "pop"=>1058, "state"=>"AK"} In order to expand this query, lets augment this method with the following:
- an optional find-by-prototype
- sorting
- paging parameters
Additionally, we will map the document term pop with an internal term
population so that we don't conflict with a reserved word later. Make sure
we keep a stable sort when manipulating the sort hash or our ordering
expressed to the DB could be randomized.
Please note that the example provided is a bit more complicated than required
because we have added a multi-level sort in addition to the field mappings.
The ordering of keys within the sort hash must state stable while performing the mapping
from population within the application to pop within the database..
def self.all(prototype={}, sort={:population=>1}, offset=0, limit=100)
#map internal :population term to :pop document term
tmp = {} #hash needs to stay in stable order provided
sort.each {|k,v|
k = k.to_sym==:population ? :pop : k.to_sym
tmp[k] = v if [:city, :state, :pop].include?(k)
}
sort=tmp
#convert to keys and then eliminate any properties not of interest
prototype=prototype.symbolize_keys.slice(:city, :state) if !prototype.nil?
Rails.logger.debug {"getting all zips, prototype=#{prototype}, sort=#{sort}, offset=#{offset}, limit=#{limit}"}
result=collection.find(prototype)
.projection({_id:true, city:true, state:true, pop:true})
.sort(sort)
.skip(offset)
result=result.limit(limit) if !limit.nil?
return result
endVerify that the default arguments to our augmented all() method returns only 100 documents. Observe
the MongoDB API debug statement to notice that the results are sorted by population.
> Zip.all.to_a.count
getting all zips, prototype={}, sort={:pop=>1}, offset=0, limit=100
zips_development.find | {"find"=>"zips", "filter"=>{},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true},
"skip"=>0, "limit"=>100, "sort"=>{"pop"=>1}}
=> 100 Verify that the former default behavior can be obtained by passing in empty values for
prototype, sort, and limit.
> Zip.all({},{},0,nil).to_a.count
getting all zips, prototype={}, sort={}, offset=0, limit=
zips_development.find | {"find"=>"zips", "filter"=>{},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true}, "skip"=>0, "sort"=>{}}
=> 29353 Lets attempt a few additional query combinations for our updated all() method
> Zip.all({'state':'NY'},{'population':-1},0,1).first
getting all zips, prototype={:state=>"NY"}, sort={:pop=>-1}, offset=0, limit=1
zips_development.find | {"find"=>"zips", "filter"=>{"state"=>"NY"},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true},
"skip"=>0, "limit"=>1, "sort"=>{"pop"=>-1}}
=> {"_id"=>"11226", "city"=>"BROOKLYN", "pop"=>111396, "state"=>"NY"}
> Zip.all({'state':'NY'},{'population':1},0,1).first
getting all zips, prototype={:state=>"NY"}, sort={:pop=>1}, offset=0, limit=1
zips_development.find {"find"=>"zips", "filter"=>{"state"=>"NY"},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true},
"skip"=>0, "limit"=>1, "sort"=>{"pop"=>1}}
=> {"_id"=>"13436", "city"=>"RAQUETTE LAKE", "pop"=>0, "state"=>"NY"} Implement the initialize() method so that it can accept a hash for both the internal (:id and :populate)
and external (:_id and :pop) views of our fields. The mapping of :_id to :id will help integrate with
Rails scaffold. The mapping from :pop to :populate helps us avoid overriding a method introduced later.
Notice the initialize() method's params hash must account for the id coming in from the view as :id,
while coming in from the database as :_id. Both Rails and MongoDB have specific names they want for this
key and we must account for this difference.
attr_accessor :id, :city, :state, :population
def to_s
"#{@id}: #{@city}, #{@state}, pop=#{@population}"
end
# initialize for both Mongo and a Web hash
def initialize(params={})
#switch between both internal and external views of id and population
@id=params[:_id].nil? ? params[:id] : params[:_id]
@city=params[:city]
@state=params[:state]
@population=params[:pop].nil? ? params[:population] : params[:pop]
endTest out the initializer by creating an instance of the Zip class from a document returned from MongoDB query.
> doc=Zip.all({'state':'NY'},{'population':-1},0,1).first
=> {"_id"=>"11226", "city"=>"BROOKLYN", "pop"=>111396, "state"=>"NY"}
> obj=Zip.new(doc)
=> #<Zip:0x0000000829c130 @id="11226", @city="BROOKLYN", @state="NY", @population=111396> Enable find() to query by the :_id=>id passed into the method. If found, return a Zip instance
initialized from the document hash returned by MongoDB.
def self.find id
Rails.logger.debug {"getting zip #{id}"}
doc=collection.find(:_id=>id)
.projection({_id:true, city:true, state:true, pop:true})
.first
return doc.nil? ? nil : Zip.new(doc)
end Lets test this method by obtaining a Zip instance for a particular zipcode and report its population.
> Zip.find("11226").population
getting zip 11226
zips_development.find | {"find"=>"zips", "filter"=>{"_id"=>"11226"},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true}}
=> 111396 The save() method should preserve the state of the current zip instance.
Note that although we are manually assigning the _id in this case and already
know the value without consulting the result. However, for dyanamic _id assignment
cases, we can get the inserted_id from the result.
def save
Rails.logger.debug {"saving #{self}"}
result=collection.insert_one(_id:@id, city:@city, state:@state, pop:@pop)
@id=result.inserted_id
endWe will create a Zip instance and then call save() to insert it into the Database
> zip=Zip.new({'id':"00001",'city':"Fake City",'state':"WY",'population':3})
=> #<Zip:0x00000006fd90c0 @id="00001", @city="Fake City", @state="WY", @population=3>
> zip.save
saving 00001: Fake City, WY, pop=3
zips_development.insert | STARTED | {"insert"=>"zips", "documents"=>[{"_id"=>"00001",
"city"=>"Fake City", "state"=>"WY", "pop"=>3}], "writeConcern"=>{"w"=>1}, "ordered"=>true}
=> "00001"Create an update() method that accepts a hash and performs an update on those
values after accounting for any name mappings.
Note that here -- again -- our method would be much simpler if we did
not have to manually map pop to population internally.
def update(updates)
Rails.logger.debug {"updating #{self} with #{updates}"}
# map internal :population term to :pop document term
updates[:pop]=updates[:population] if !updates[:population].nil?
updates.slice!(:city, :state, :pop) if !slice.nil?
self.class.collection
.find(_id:@id)
.update_one(:$set=>updates)
endTo test update() we will obtain a zip instance using find(), then update its population from 3 to 4.
Notice that :$set was used to change specific field(s), without changing fields not supplied.
> zip=Zip.find "00001"
getting zip 00001
=> #<Zip:0x00000005835f68 @id="00001", @city="Fake City", @state="WY", @population=3>
> zip.update({:population=>4})
updating 00001: Fake City, WY, pop=3 with {:population=>4}
zips_development.update | {"update"=>"zips", "updates"=>[{"q"=>{:_id=>"00001"},
"u"=>{:$set=>{:pop=>4}}, "multi"=>false, "upsert"=>false}], "writeConcern"=>{:w=>1}, "ordered"=>true}
=> #<Mongo::Operation::Result:46208000 documents=[{"ok"=>1, "nModified"=>1, "n"=>1}]>
> zip=Zip.find("00001").population
getting zip 00001
=> 4 destroy() will delete the document from the database that is associated with
the instance's :id.
def destroy
Rails.logger.debug {"destroying #{self}"}
self.class.collection
.find(_id:@id)
.delete_one
endLoad an instance with the state of one of the cities and remove that city from the database.
> zip=Zip.find "00001"
getting zip 00001
zips_development.find | {"find"=>"zips", "filter"=>{"_id"=>"00001"},
"projection"=>{"_id"=>true, "city"=>true, "state"=>true, "pop"=>true}}
=> #<Zip:0x0000000647cee8 @id="00001", @city="Fake City", @state="WY", @population=4>
> zip.destroy
destroying 00001: Fake City, WY, pop=4
zips_development.delete | {"delete"=>"zips",
"deletes"=>[{"q"=>{"_id"=>"00001"}, "limit"=>1}], "writeConcern"=>{"w"=>1}, "ordered"=>true}
=> #<Mongo::Operation::Result:52648160 documents=[{"ok"=>1, "n"=>1}]>
> zip=Zip.find "00001"
getting zip 00001
=> nil We need to include the ActiveModel::Model mixin and override its persisted? implementation
to simply return the result of whether a primary key has been assigned. JSON marshalling
will also expect a created_at and updated_at by default.
class Zip
include ActiveModel::Model
...
def persisted?
!@id.nil?
end
def created_at
nil
end
def updated_at
nil
endOur class should now be at a point where it can integrate with Rails as an official Model class -- with some help.
class Zip
include ActiveModel::Model
attr_accessor :id, :city, :state, :population
def to_s
"#{@id}: #{@city}, #{@state}, pop=#{@population}"
end
# initialize from both a Mongo and Web hash
def initialize(params={})
#switch between both internal and external views of id and population
@id=params[:_id].nil? ? params[:id] : params[:_id]
@city=params[:city]
@state=params[:state]
@population=params[:pop].nil? ? params[:population] : params[:pop]
end
# tell Rails whether this instance is persisted
def persisted?
!@id.nil?
end
def created_at
nil
end
def updated_at
nil
end
# convenience method for access to client in console
def self.mongo_client
Mongoid::Clients.default
end
# convenience method for access to zips collection
def self.collection
self.mongo_client['zips']
end
# implement a find that returns a collection of document as hashes.
# Use initialize(hash) to express individual documents as a class
# instance.
# * prototype - query example for value equality
# * sort - hash expressing multi-term sort order
# * offset - document to start results
# * limit - number of documents to include
def self.all(prototype={}, sort={:population=>1}, offset=0, limit=100)
#map internal :population term to :pop document term
tmp = {} #hash needs to stay in stable order provided
sort.each {|k,v|
k = k.to_sym==:population ? :pop : k.to_sym
tmp[k] = v if [:city, :state, :pop].include?(k)
}
sort=tmp
#convert to keys and then eliminate any properties not of interest
prototype=prototype.symbolize_keys.slice(:city, :state) if !prototype.nil?
Rails.logger.debug {"getting all zips, prototype=#{prototype}, sort=#{sort}, offset=#{offset}, limit=#{limit}"}
result=collection.find(prototype)
.projection({_id:true, city:true, state:true, pop:true})
.sort(sort)
.skip(offset)
result=result.limit(limit) if !limit.nil?
return result
end
# locate a specific document. Use initialize(hash) on the result to
# get in class instance form
def self.find id
Rails.logger.debug {"getting zip #{id}"}
doc=collection.find(:_id=>id)
.projection({_id:true, city:true, state:true, pop:true})
.first
return doc.nil? ? nil : Zip.new(doc)
end
# create a new document using the current instance
def save
Rails.logger.debug {"saving #{self}"}
self.class.collection
.insert_one(_id:@id, city:@city, state:@state, pop:@population)
end
# update the values for this instance
def update(updates)
Rails.logger.debug {"updating #{self} with #{updates}"}
#map internal :population term to :pop document term
updates[:pop]=updates[:population] if !updates[:population].nil?
updates.slice!(:city, :state, :pop) if !updates.nil?
self.class.collection
.find(_id:@id)
.update_one(:$set=>updates)
end
# remove the document associated with this instance form the DB
def destroy
Rails.logger.debug {"destroying #{self}"}
self.class.collection
.find(_id:@id)
.delete_one
end
endGenerate the controller and view using a scaffold command that does not create a model class.
$ rails g scaffold_controller Zip id city state population:integer
create app/controllers/zips_controller.rb
invoke erb
create app/views/zips
create app/views/zips/index.html.erb
create app/views/zips/edit.html.erb
create app/views/zips/show.html.erb
create app/views/zips/new.html.erb
create app/views/zips/_form.html.erb
invoke test_unit
create test/controllers/zips_controller_test.rb
invoke helper
create app/helpers/zips_helper.rb
invoke test_unit
invoke jbuilder
create app/views/zips/index.json.jbuilder
create app/views/zips/show.json.jbuilderVerify the route to the new controller is in place in config/routes.rb
Rails.application.routes.draw do
resources :zipsAccess the new page and observe an error between what was returned
by the Model (a hash with an :_id key) and what is required by the view
(an instance of a class with the id() method).
http://localhost:3000/zips
undefined method `id' for {"_id"=>"99773", "city"=>"SHUNGNAK", "pop"=>0, "state"=>"AK"}:BSON::Document
<% @zips.each do |zip| %>
<tr>
<td><%= zip.id %></td>
<td><%= zip.city %></td>
<td><%= zip.state %></td>
<td><%= zip.population %></td>
Add the following helper method to the app/helpers/zips_helper.rb to
convert a Mongo document to a Ruby class instance. We left it as a document
so the all() method did not have to EAGERly access every document in the result
set before it was know it would be used.
module ZipsHelper
def toZip(value)
#change value to a Zip if not already a Zip
return value.is_a?(Zip) ? value : Zip.new(value)
end
endAdd a call to the helper method in app/views/zips/index.html.erb
<% @zips.each do |zip| %>
<% zip=toZip(zip) %>Not there is a similar issue with the JSON view as well
http://localhost:3000/zips.json
key not found: :id
json.array!(@zips) do |zip|
json.extract! zip, :id, :id, :city, :state, :population
json.url zip_url(zip, format: :json)
endFix the JSON view error by calling the helper method in app/helpers/zips/index.json.jbuilder.
json.array!(@zips) do |zip|
zip=toZip(zip)
json.extract! zip, :id, :id, :city, :state, :population
json.url zip_url(zip, format: :json)
endIn our ZipsController, prior to a :show, :edit, :update, or :destroy action being executed,
we will first invoke the set_zip() method to retreive the specific Zip by id. The generated helper
comes ready to call Zip.find and expect to get an instance back.
The zip_params() method restricts mass assignments for :zip parameters to the fields of
:id, :city, :state, and :populaton.
class ZipsController < ApplicationController
before_action :set_zip, only: [:show, :edit, :update, :destroy]
private
# Use callbacks to share common setup or constraints between actions.
def set_zip
@zip = Zip.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def zip_params
params.require(:zip).permit(:id, :city, :state, :population)
endindex() retrieves all the Zips. The generated action method comes ready to call Zip.all.
http://localhost:3000/zips
http://localhost:3000/zips.json
def index
@zips = Zip.all
end
show() retrieves a specific Zip based upon its id. The generated action method is fully
implemented by the generated set_zip helper method.
#GET /zips/{id}
#GET /zips/{id}.json
before_action :set_zip, only: [:show, :edit, :update, :destroy]
def set_zip
@zip = Zip.find(params[:id])
end
def show
endnew() returns an initial prototype to the form to create a new Zip.
create() accepts the results and creates a new Zip instance in the database.
Both of these generated action methods come ready to call Zip.new, which uses the initialize
method. The generated create action also comes ready to invoke save on the Zip instance.
#POST /zips/new
def new
@zip = Zip.new
end
#POST /zips
def create
@zip = Zip.new(zip_params)
respond_to do |format|
if @zip.save
format.html { redirect_to @zip, notice: 'Zip was successfully created.' }
format.json { render :show, status: :created, location: @zip }
else
format.html { render :new }
format.json { render json: @zip.errors, status: :unprocessable_entity }
end
end
endedit() retrieves the instance from the database based upon its id.
update() applies the changes to the retrieved instance provided by edit().
Both generated action methods rely on the before_action and the generated update
action also comes ready to call update on the Zip instance.
http://localhost:3000/zips/00002/edit
#GET /zips/{id}
before_action :set_zip, only: [:show, :edit, :update, :destroy]
def set_zip
@zip = Zip.find(params[:id])
end
def edit
end
#PUT /zips/{id}
def update
respond_to do |format|
if @zip.update(zip_params)
format.html { redirect_to @zip, notice: 'Zip was successfully updated.' }
format.json { render :show, status: :ok, location: @zip }
else
format.html { render :edit }
format.json { render json: @zip.errors, status: :unprocessable_entity }
end
end
enddestroy() removes a specific Zip instance based upon its id. The generated action
method comes ready to call destroy on the Zip instance.
#DELETE /zips/{id}
def destroy
@zip.destroy
respond_to do |format|
format.html { redirect_to zips_url, notice: 'Zip was successfully destroyed.' }
format.json { head :no_content }
end
endAdd a second line to config/routes.rb to make zips the root application.
Rails.application.routes.draw do
root 'zips#index'Add Pagination
gem 'will_paginate', '~> 3.0.7'bundleThis command will add page properties to the displayed view and
controls advance paging. Specifically, it can pass :page as
a number >= 1. will_paginate also uses :per_page to express the
row limit for a single page.
<table>
...
<tbody>
<% @zips.each do |zip| %>
<% zip=toZip(zip) %>
<tr>
<td><%= zip.id %></td>
...
</tr>
<% end %>
</tbody>
</table>
<%= will_paginate @zips %>The controller is passing a controlled set of parameters to the
Model.paginate call. The page number is currently the only value passed
but :per_page could be specified here as well.
def index
#@zips = Zip.all
@zips = Zip.paginate(:page => params[:page])
end This method implements a facade around the all() method by translating the
will_paginate inputs into all() query inputs and converts the document
array results into a will_paginate result that contains such things as
total number of documents.
def self.paginate(params)
Rails.logger.debug("paginate(#{params})")
page=(params[:page] ||= 1).to_i
limit=(params[:per_page] ||= 30).to_i
offset=(page-1)*limit
#get the associated page of Zips -- eagerly convert doc to Zip
zips=[]
all({}, {}, offset, limit).each do |doc|
zips << Zip.new(doc)
end
#get a count of all documents in the collection
total=all({}, {}, 0, 1).count
WillPaginate::Collection.create(page, limit, total) do |pager|
pager.replace(zips)
end
endA reference on how to use will_paginate
As a quick test to verify our current will_paginate implementation, the following URL should land us on the 3rd page of "Listing Zips"
http://localhost:3000/?page=3
Given the following URL:
http://localhost:3000/?page=38&per_page=10&sort=population:-1,city:1&state=MD
Lets implement additional functionality that will allow our application to:
- Specify a particular page number:
page=38 - Define a row limit per page:
per_page=10 - Order the results by population DESC, city ASC:
sort=population:-1,city:1 - Define zips for a particular state:
state=MD
Create a Helper Method in the Controller to Convert the Sort Query Param to a MongoDB Query Sort Hash
app/controller/zips_controller.rb
private
#create a hash sort spec from query param
#sort=state:1,city,population:-1
#{state:1, city:1, population:-1}
def get_sort_hash(sort)
order={}
if (!sort.nil?)
sort.split(",").each do |term|
args=term.split(":")
dir = args.length<2 || args[1].to_i >= 0 ? 1 : -1
order[args[0]] = dir
end
end
return order
endThis passes right to our Model.paginate call where we can add a small amount of processing to pass it
through to the all() method.
def index
#@zips = Zip.all
#@zips = Zip.paginate(params)
args=params.clone #update a clone of params
args[:sort]=get_sort_hash(args[:sort]) #replace sort with hash
@zips = Zip.paginate(args)
endapp/models/zip.rb
def self.paginate(params)
...
sort=params[:sort] ||= {}
...
all(params, sort, offset, limit).each do |doc|
...
total=all(params, sort, 0, 1).count
...
endWith our selection criteria and ordering logic now in place, the below URL:
http://localhost:3000/?page=38&per_page=10&sort=population:-1,city:1&state=MD
should render us results similiar to this:
###Listing Zips
| Id | City | State | Population | |||
|---|---|---|---|---|---|---|
| 21522 | BITTINGER | MD | 479 | Show | Edit | Destroy |
| 21156 | Upper Falls | MD | 464 | Show | Edit | Destroy |
| 20632 | FAULKNER | MD | 459 | Show | Edit | Destroy |
| 21677 | WOOLFORD | MD | 459 | Show | Edit | Destroy |
| 21816 | CHANCE | MD | 415 | Show | Edit | Destroy |
| 20630 | DRAYDEN | MD | 413 | Show | Edit | Destroy |
| 20779 | TRACYS LANDING | MD | 413 | Show | Edit | Destroy |
| 20615 | BROOMES ISLAND | MD | 404 | Show | Edit | Destroy |
| 21672 | TODDVILE | MD | 361 | Show | Edit | Destroy |
| 21840 | NANTICOKE | MD | 358 | Show | Edit | Destroy |
<-- Previous 1 2 ... 34 35 36 37 38 39 40 41 42 Next -->
If you flip the value of city:1 from 1 to -1, should will see the ordering of DRAYDEN and
TRACYS LANDING switch places.
-
Create an
mLab Accounthttps://mlab.com/home -
Create a (Free Sandbox) Database on mLab
-
From your mLab home page, select
Create NewMongoDB Deployments -
For
Cloud Provider:selectAmazon Web Services- On the
Location:Pull Down Menu, select the Region that is geographically closest to you
- On the
-
Under Plan: select the
Single-nodeOption- Select the Free
Sanboxoption underStandard Line
- Select the Free
-
Leave the
High Storage Lineoptions blank -
In the
Database name:input field, supply a name (i.e., zips_production) -
Verify the
Price:field calculator is$0 / month -
Select the
Create new MongoDB deploymentbutton -
On your mLab home page, you should now see your database (i.e., zips_production) listed
-
Now select your newly created MongoDB deployment
-
The following URL template should be displayed with the details pertaining to your deployment
To connect using a driver via the standard MongoDB URI: mongodb://<dbuser>:<dbpass>@<dbhost>/<dbname>
-
-
Create a Database User and Password on mLab
- Select the
UsersMenu - Select the
Add database userbutton - In the
Add new database userform, supply a username and password:<dbuser><dbpass>- then select
Create
- Select the
-
Import
zips.jsonfrom MongoDB using the database and user account created above$ wget http://media.mongodb.org/zips.json $ mongoimport -h dbhost -d dbname -c zips -u dbuser -p dbpass --file zips.json 2015-12-07T18:03:34.015-0500 connected to: ds######.mlab.com:<port> 2015-12-07T18:03:36.416-0500 [################........] <dbname>.<collection_name> 2.1 MB/3.0 MB (68.5%) 2015-12-07T18:03:38.953-0500 imported 29353 documents
-
Create a
Heroku Account- If not yet installed, download and install the Heroku Toolbelt.
- This client CLI will be used in later steps that rely on
herokucommands
-
Register your application with Heroku by changing to the directory with a git repository and invoking
heroku apps:create (appname).Note that your application must be in the root directory of the development folder hosting the git repository.
$ cd zips $ heroku apps:create appname Creating appname... done, stack is cedar-14 https://appname.herokuapp.com/ | https://git.heroku.com/appname.git Git remote heroku addedThis will add an additional remote to your git repository.
$ git remote --verbose heroku https://git.heroku.com/appname.git (fetch) heroku https://git.heroku.com/appname.git (push) ...
-
Add a
MONGOLAB_URIenvironment variable wheredbhostis both host and port# concatenated together, separated by a ":" (host:port) .$ heroku config:add MONGOLAB_URI=mongodb://dbuser:dbpass@dbhost/dbname
-
Add a
productionprofile to theconfig/mongoid.ymlfile. The followingMongoid connection informationwas provided by theMongoLabpage on theHerokuDev Center page.production: clients: default: uri: <%= ENV['MONGOLAB_URI'] %> options: connect_timeout: 15
-
Update the
Gemfileso that Heroku will accept and deploy our application.Restrict the
sqlitegem inGemfileto the development profile. Heroku does not supportsqliteand this application does not use anRDBMS. However, this gem was put there by byrails newby default and required to stick around because we have not removed ActiveRecord from the application.gem 'sqlite3', group: :development
Add the postgres gem to the production profile. We have not neutered the application of ActiveRecord and Heroku wants a supported database for that platform.
group :production do #use postgres on heroku gem 'pg' gem 'rails_12factor' end
Be sure to run bundle when complete and check the Gemfile.lock file into git.
$ bundle
-
Commit changes to application
$ git commit -am "ready for heroku deploy" -
Deploy application
$ git push heroku master
-
Access URL
http://appname.herokuapp.com
-
Access logs
$ heroku logs