forked from jhu-ep-coursera/fullstack-course3-module1-zips
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.html
878 lines (810 loc) · 69.2 KB
/
README.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
<h1 id="integrating-mongo-ruby-api-with-rails">Integrating Mongo Ruby API with Rails</h1>
<p>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 <code>all()</code> and <code>find()</code> methods in addition to convenience methods to return the <code>mongo_client</code> and <code>collection</code>. Instances of the class will represent a specific document and its properties. The <code>save()</code>, <code>update()</code>, and <code>destroy()</code> 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.</p>
<h2 id="create-application-and-configure-mongodb-connection">Create Application and Configure MongoDB Connection</h2>
<h3 id="create-a-new-application">Create a new application</h3>
<pre class="shell"><code>$ rails new zips</code></pre>
<h3 id="add-mongoid-to-application-to-supply-mongodb-connection">Add Mongoid to Application to supply MongoDB Connection</h3>
<p>Add the Mongoid gem to the <code>Gemfile</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">gem <span class="st">'mongoid'</span>, <span class="st">'~> 5.0.0'</span></code></pre>
<p>Run the bundler</p>
<pre class="shell"><code>$ bundle</code></pre>
<p>Generate a mongo database configuration file.</p>
<pre class="shell"><code>$ rails g mongoid:config
(output:)
create config/mongoid.yml</code></pre>
<p>While the defaults generated from the above command are fine, do make sure that the names defined for <code>development:clients:default:database</code> and <code>test:clients:default:database</code> agree with where you import the JSON in a later step.</p>
<pre class="shell"><code>$ 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: 1</code></pre>
<p>Next, we need to add some configurations to <code>config/application.rb</code>. This is used by stand-alone programs like <code>rails console</code> 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.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">module</span> <span class="dt">Zips</span>
<span class="kw">class</span> <span class="dt">Application</span> < <span class="dt">Rails</span>::<span class="dt">Application</span>
...
<span class="co"># bootstraps mongoid within applications -- like rails console</span>
<span class="dt">Mongoid</span>.load!(<span class="st">'./config/mongoid.yml'</span>)
<span class="co"># which default ORM are we using with scaffold</span>
<span class="co"># add --orm none, mongoid, or active_record </span>
<span class="co"># to rails generate cmd line to be specific</span>
config.generators {|g| g.orm <span class="st">:active_record</span>}
<span class="co"># config.generators {|g| g.orm :mongoid}</span>
<span class="co"># Do not swallow errors in after_commit/after_rollback callbacks.</span>
config.active_record.raise_in_transactional_callbacks = <span class="dv">true</span>
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<h3 id="start-web-server">Start Web Server</h3>
<pre class="shell"><code>$ rails s</code></pre>
<h3 id="download-zips.json-from-mongodb-web-site-and-import">Download zips.json from MongoDB Web Site and Import</h3>
<p>Download the <a href="http://media.mongodb.org/zips.json"><code>zips.json</code></a> from the MongoDB web site. The following shows an example of downloading it using <code>curl</code>. You may use a web browser. Place the downloaded file in <code>db/zips.json</code>, relative to the root of your application. The <code>db</code> directory should already exist.</p>
<pre class="shell"><code>curl http://media.mongodb.org/zips.json -o db/zips.json</code></pre>
<p>Import the <code>zips.json</code> into the <code>zips</code> collection in the <code>zips_development</code> database.</p>
<p><strong>Note 1: The <code>--db</code> name must match the name defined in <code>config/mongoid.yml</code></strong></p>
<p><strong>Note 2: If not yet running, start your MongoDB Server using <code>mongod</code></strong></p>
<pre class="shell"><code>$ 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 documents</code></pre>
<p>To help familiarize you with the data we just imported, below is a represtantive document from the <code>zips.json</code> data set</p>
<pre class="sourceCode json"><code class="sourceCode json">{
<span class="dt">"_id"</span> : <span class="st">"01007"</span>,
<span class="dt">"city"</span> : <span class="st">"BELCHERTOWN"</span>,
<span class="dt">"loc"</span> : [ <span class="fl">-72.410953</span>, <span class="fl">42.275103</span> ],
<span class="dt">"pop"</span> : <span class="dv">10579</span>,
<span class="dt">"state"</span> : <span class="st">"MA"</span>
}</code></pre>
<h2 id="create-a-zip-class-to-access-the-database">Create a Zip Class to Access the Database</h2>
<p>We will be manually creating an <code>app/model/zip.rb</code> 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 <code>zip.rb</code> model will implement the following:</p>
<ul>
<li>a <code>mongo_client</code> class method that returns the default database connection</li>
<li>a <code>collection</code> class method that returns a reference to the zips collection.</li>
</ul>
<p>These methods are consistent with methods implemented when using mongoid ORM.</p>
<h3 id="add-the-mongo_client-and-collection-class-methods">Add the <code>mongo_client</code> and <code>collection</code> class methods</h3>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">Zip</span>
<span class="co"># convenience method for access to client in console</span>
<span class="kw">def</span> <span class="dv">self</span>.mongo_client
<span class="dt">Mongoid</span>::<span class="dt">Clients</span>.default
<span class="kw">end</span>
<span class="co"># convenience method for access to zips collection</span>
<span class="kw">def</span> <span class="dv">self</span>.collection
<span class="dv">self</span>.mongo_client[<span class="st">'zips'</span>]
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<p>In the <code>rails console</code>, invoke the <code>zip</code> 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 <code>mongoimport</code> command and should contain the zip codes imported from <code>zips.json</code> into our <code>zips</code> collection.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">$ rails c
<span class="dt">Loading</span> development environment (<span class="dt">Rails</span> <span class="fl">4.2</span>.<span class="dv">3</span>)
> <span class="dt">Zip</span>.mongo_client
=> <span class="co">#<Mongo::Client:0x34369420 cluster=localhost:27017></span>
> <span class="dt">Zip</span>.mongo_client[<span class="st">:zips</span>]
=> <span class="co">#<Mongo::Collection:0x22032060 namespace=zips_development.zips></span>
> <span class="dt">Zip</span>.collection
=> <span class="co">#<Mongo::Collection:0x21998940 namespace=zips_development.zips></span>
> <span class="dt">Zip</span>.collection.find.count
<span class="dt">DEBUG</span> | zips_development.count | <span class="dt">STARTED</span> | {<span class="st">"count"</span>=><span class="st">"zips"</span>, <span class="st">"query"</span>=>{}}
=> <span class="dv">29353</span> </code></pre>
<h2 id="update-the-zip-class-with-crud-methods">Update the Zip Class with CRUD Methods</h2>
<p>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.</p>
<h3 id="add-an-all-class-method-to-find-all-documents">Add an <code>all()</code> Class Method to Find All Documents</h3>
<p>Lets add a method that will return all documents from the zips collection. We will be starting with a <a href="https://docs.mongodb.org/manual/reference/glossary/#term-projection">projection</a> that only returns the fields of interest -- in our case: <code>_id</code>, <code>city</code>, <code>state</code>, and <code>pop</code>. Since we started off earlier examples by eliminating location, I am choosing to show a projection that eliminates that property from our output.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> <span class="dv">self</span>.all
collection.find
.projection({_id<span class="st">:true</span>, city<span class="st">:true</span>, state<span class="st">:true</span>, pop<span class="st">:true</span>})
<span class="kw">end</span> </code></pre>
<p>Within the <code>rails console</code>, verify we can use our implementation of the <code>all()</code> method that also omits the <code>location</code> property.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">Zip</span>.all.count
=> <span class="dv">29353</span>
> <span class="dt">Zip</span>.all.first
=> {<span class="st">"_id"</span>=><span class="st">"99743"</span>, <span class="st">"city"</span>=><span class="st">"HEALY"</span>, <span class="st">"pop"</span>=><span class="dv">1058</span>, <span class="st">"state"</span>=><span class="st">"AK"</span>} </code></pre>
<p>In order to expand this query, lets augment this method with the following:</p>
<ul>
<li>an optional find-by-prototype</li>
<li>sorting</li>
<li>paging parameters</li>
</ul>
<p>Additionally, we will map the document term <code>pop</code> with an internal term <code>population</code> 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.</p>
<p>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 <code>population</code> within the application to <code>pop</code> within the database..</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> <span class="dv">self</span>.all(prototype={}, sort={<span class="st">:population=</span>><span class="dv">1</span>}, offset=<span class="dv">0</span>, limit=<span class="dv">100</span>)
<span class="co">#map internal :population term to :pop document term</span>
tmp = {} <span class="co">#hash needs to stay in stable order provided</span>
sort.each {|k,v|
k = k.to_sym==<span class="st">:population</span> ? <span class="st">:pop</span> : k.to_sym
tmp[k] = v <span class="kw">if</span> [<span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:pop</span>].include?(k)
}
sort=tmp
<span class="co">#convert to keys and then eliminate any properties not of interest</span>
prototype=prototype.symbolize_keys.slice(<span class="st">:city</span>, <span class="st">:state</span>) <span class="kw">if</span> !prototype.nil?
<span class="dt">Rails</span>.logger.debug {<span class="st">"getting all zips, prototype=</span><span class="ot">#{</span>prototype<span class="ot">}</span><span class="st">, sort=</span><span class="ot">#{</span>sort<span class="ot">}</span><span class="st">, offset=</span><span class="ot">#{</span>offset<span class="ot">}</span><span class="st">, limit=</span><span class="ot">#{</span>limit<span class="ot">}</span><span class="st">"</span>}
result=collection.find(prototype)
.projection({_id<span class="st">:true</span>, city<span class="st">:true</span>, state<span class="st">:true</span>, pop<span class="st">:true</span>})
.sort(sort)
.skip(offset)
result=result.limit(limit) <span class="kw">if</span> !limit.nil?
<span class="kw">return</span> result
<span class="kw">end</span></code></pre>
<p>Verify that the default arguments to our augmented <code>all()</code> method returns only 100 documents. Observe the MongoDB API debug statement to notice that the results are sorted by population.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">Zip</span>.all.to_a.count
getting all zips, prototype={}, sort={<span class="st">:pop=</span>><span class="dv">1</span>}, offset=<span class="dv">0</span>, limit=<span class="dv">100</span>
zips_development.find | {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>},
<span class="st">"skip"</span>=><span class="dv">0</span>, <span class="st">"limit"</span>=><span class="dv">100</span>, <span class="st">"sort"</span>=>{<span class="st">"pop"</span>=><span class="dv">1</span>}}
=> <span class="dv">100</span> </code></pre>
<p>Verify that the former default behavior can be obtained by passing in empty values for <code>prototype</code>, <code>sort</code>, and <code>limit</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">Zip</span>.all({},{},<span class="dv">0</span>,<span class="dv">nil</span>).to_a.count
getting all zips, prototype={}, sort={}, offset=<span class="dv">0</span>, limit=
zips_development.find | {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>}, <span class="st">"skip"</span>=><span class="dv">0</span>, <span class="st">"sort"</span>=>{}}
=> <span class="dv">29353</span> </code></pre>
<p>Lets attempt a few additional query combinations for our updated <code>all()</code> method</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">Zip</span>.all({<span class="st">'state'</span>:<span class="st">'NY'</span>},{<span class="st">'population'</span>:-<span class="dv">1</span>},<span class="dv">0</span>,<span class="dv">1</span>).first
getting all zips, prototype={<span class="st">:state=</span>><span class="st">"NY"</span>}, sort={<span class="st">:pop=</span>>-<span class="dv">1</span>}, offset=<span class="dv">0</span>, limit=<span class="dv">1</span>
zips_development.find | {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{<span class="st">"state"</span>=><span class="st">"NY"</span>},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>},
<span class="st">"skip"</span>=><span class="dv">0</span>, <span class="st">"limit"</span>=><span class="dv">1</span>, <span class="st">"sort"</span>=>{<span class="st">"pop"</span>=>-<span class="dv">1</span>}}
=> {<span class="st">"_id"</span>=><span class="st">"11226"</span>, <span class="st">"city"</span>=><span class="st">"BROOKLYN"</span>, <span class="st">"pop"</span>=><span class="dv">111396</span>, <span class="st">"state"</span>=><span class="st">"NY"</span>}
> <span class="dt">Zip</span>.all({<span class="st">'state'</span>:<span class="st">'NY'</span>},{<span class="st">'population'</span>:<span class="dv">1</span>},<span class="dv">0</span>,<span class="dv">1</span>).first
getting all zips, prototype={<span class="st">:state=</span>><span class="st">"NY"</span>}, sort={<span class="st">:pop=</span>><span class="dv">1</span>}, offset=<span class="dv">0</span>, limit=<span class="dv">1</span>
zips_development.find {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{<span class="st">"state"</span>=><span class="st">"NY"</span>},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>},
<span class="st">"skip"</span>=><span class="dv">0</span>, <span class="st">"limit"</span>=><span class="dv">1</span>, <span class="st">"sort"</span>=>{<span class="st">"pop"</span>=><span class="dv">1</span>}}
=> {<span class="st">"_id"</span>=><span class="st">"13436"</span>, <span class="st">"city"</span>=><span class="st">"RAQUETTE LAKE"</span>, <span class="st">"pop"</span>=><span class="dv">0</span>, <span class="st">"state"</span>=><span class="st">"NY"</span>} </code></pre>
<h3 id="add-instance-support-for-the-fields-and-add-the-ability-to-initialize-from-a-hash">Add Instance Support for the Fields and Add the Ability to Initialize from a Hash</h3>
<p>Implement the <code>initialize()</code> method so that it can accept a hash for both the internal (<code>:id</code> and <code>:populate</code>) and external (<code>:_id</code> and <code>:pop</code>) views of our fields. The mapping of <code>:_id</code> to <code>:id</code> will help integrate with Rails scaffold. The mapping from <code>:pop</code> to <code>:populate</code> helps us avoid overriding a method introduced later.</p>
<p>Notice the <code>initialize()</code> method's <code>params</code> hash must account for the <code>id</code> coming in from the view as <code>:id</code>, while coming in from the database as <code>:_id</code>. Both Rails and MongoDB have specific names they want for this key and we must account for this difference.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="ot">attr_accessor</span> <span class="st">:id</span>, <span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:population</span>
<span class="kw">def</span> to_s
<span class="st">"</span><span class="ot">#{@id}</span><span class="st">: </span><span class="ot">#{@city}</span><span class="st">, </span><span class="ot">#{@state}</span><span class="st">, pop=</span><span class="ot">#{@population}</span><span class="st">"</span>
<span class="kw">end</span>
<span class="co"># initialize for both Mongo and a Web hash</span>
<span class="kw">def</span> initialize(params={})
<span class="co">#switch between both internal and external views of id and population</span>
<span class="ot">@id</span>=params[<span class="st">:_id</span>].nil? ? params[<span class="st">:id</span>] : params[<span class="st">:_id</span>]
<span class="ot">@city</span>=params[<span class="st">:city</span>]
<span class="ot">@state</span>=params[<span class="st">:state</span>]
<span class="ot">@population</span>=params[<span class="st">:pop</span>].nil? ? params[<span class="st">:population</span>] : params[<span class="st">:pop</span>]
<span class="kw">end</span></code></pre>
<p>Test out the initializer by creating an instance of the Zip class from a document returned from MongoDB query.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> doc=<span class="dt">Zip</span>.all({<span class="st">'state'</span>:<span class="st">'NY'</span>},{<span class="st">'population'</span>:-<span class="dv">1</span>},<span class="dv">0</span>,<span class="dv">1</span>).first
=> {<span class="st">"_id"</span>=><span class="st">"11226"</span>, <span class="st">"city"</span>=><span class="st">"BROOKLYN"</span>, <span class="st">"pop"</span>=><span class="dv">111396</span>, <span class="st">"state"</span>=><span class="st">"NY"</span>}
> obj=<span class="dt">Zip</span>.new(doc)
=> <span class="co">#<Zip:0x0000000829c130 @id="11226", @city="BROOKLYN", @state="NY", @population=111396> </span></code></pre>
<h3 id="add-a-find-class-method-to-findreturn-a-specific-instance">Add a <code>find()</code> Class Method to Find/Return a Specific Instance</h3>
<p>Enable <code>find()</code> to query by the <code>:_id=>id</code> passed into the method. If found, return a <code>Zip</code> instance initialized from the document hash returned by MongoDB.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> <span class="dv">self</span>.find id
<span class="dt">Rails</span>.logger.debug {<span class="st">"getting zip </span><span class="ot">#{</span>id<span class="ot">}</span><span class="st">"</span>}
doc=collection.find(<span class="st">:_id=</span>>id)
.projection({_id<span class="st">:true</span>, city<span class="st">:true</span>, state<span class="st">:true</span>, pop<span class="st">:true</span>})
.first
<span class="kw">return</span> doc.nil? ? <span class="dv">nil</span> : <span class="dt">Zip</span>.new(doc)
<span class="kw">end</span> </code></pre>
<p>Lets test this method by obtaining a <code>Zip</code> instance for a particular zipcode and report its population.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">Zip</span>.find(<span class="st">"11226"</span>).population
getting zip <span class="dv">11226</span>
zips_development.find | {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{<span class="st">"_id"</span>=><span class="st">"11226"</span>},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>}}
=> <span class="dv">111396</span> </code></pre>
<h3 id="implement-a-save-instance-method-to-insert-a-new-zip">Implement a <code>save()</code> Instance Method to Insert a New Zip</h3>
<p>The <code>save()</code> method should preserve the state of the current <code>zip</code> instance.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> save
<span class="dt">Rails</span>.logger.debug {<span class="st">"saving </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st">"</span>}
collection.insert_one(_id<span class="st">:@id</span>, city<span class="st">:@city</span>, state<span class="st">:@state</span>, pop<span class="st">:@pop</span>)
<span class="kw">end</span></code></pre>
<p>We will create a <code>Zip</code> instance and then call <code>save()</code> to insert it into the Database</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> zip=<span class="dt">Zip</span>.new({<span class="st">'id'</span>:<span class="st">"00001"</span>,<span class="st">'city'</span>:<span class="st">"Fake City"</span>,<span class="st">'state'</span>:<span class="st">"WY"</span>,<span class="st">'population'</span>:<span class="dv">3</span>})
=> <span class="co">#<Zip:0x00000006fd90c0 @id="00001", @city="Fake City", @state="WY", @population=3> </span>
> zip.save
saving <span class="dv">00001</span>: <span class="dt">Fake</span> <span class="dt">City</span>, <span class="dt">WY</span>, pop=<span class="dv">3</span>
zips_development.insert | <span class="dt">STARTED</span> | {<span class="st">"insert"</span>=><span class="st">"zips"</span>, <span class="st">"documents"</span>=>[{<span class="st">"_id"</span>=><span class="st">"00001"</span>,
<span class="st">"city"</span>=><span class="st">"Fake City"</span>, <span class="st">"state"</span>=><span class="st">"WY"</span>, <span class="st">"pop"</span>=><span class="dv">3</span>}], <span class="st">"writeConcern"</span>=>{<span class="st">"w"</span>=><span class="dv">1</span>}, <span class="st">"ordered"</span>=><span class="dv">true</span>}
=> <span class="co">#<Mongo::Operation::Result:54733380 documents=[{"ok"=>1, "n"=>1}]> </span></code></pre>
<h3 id="add-an-update-instance-method-to-change-the-values-in-the-database">Add an <code>update()</code> Instance Method to Change the Values in the Database</h3>
<p>Create an <code>update()</code> method that accepts a hash and performs an update on those values after accounting for any name mappings.</p>
<p>Note that here -- again -- our method would be much simpler if we did not have to manually map <code>pop</code> to <code>population</code> internally.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> update(updates)
<span class="dt">Rails</span>.logger.debug {<span class="st">"updating </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st"> with </span><span class="ot">#{</span>updates<span class="ot">}</span><span class="st">"</span>}
<span class="co"># map internal :population term to :pop document term</span>
updates[<span class="st">:pop</span>]=updates[<span class="st">:population</span>] <span class="kw">if</span> !updates[<span class="st">:population</span>].nil?
updates.slice!(<span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:pop</span>) <span class="kw">if</span> !slice.nil?
<span class="dv">self</span>.class.collection
.find(_id<span class="st">:@id</span>)
.update_one(<span class="st">:$set=</span>>updates)
<span class="kw">end</span></code></pre>
<p>To test <code>update()</code> we will obtain a zip instance using <code>find()</code>, then update its <code>population</code> from 3 to 4. Notice that <code>:$set</code> was used to change specific field(s), without changing fields not supplied.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> zip=<span class="dt">Zip</span>.find <span class="st">"00001"</span>
getting zip <span class="dv">00001</span>
=> <span class="co">#<Zip:0x00000005835f68 @id="00001", @city="Fake City", @state="WY", @population=3> </span>
> zip.update({<span class="st">:population=</span>><span class="dv">4</span>})
updating <span class="dv">00001</span>: <span class="dt">Fake</span> <span class="dt">City</span>, <span class="dt">WY</span>, pop=<span class="dv">3</span> with {<span class="st">:population=</span>><span class="dv">4</span>}
zips_development.update | {<span class="st">"update"</span>=><span class="st">"zips"</span>, <span class="st">"updates"</span>=>[{<span class="st">"q"</span>=>{<span class="st">:_id=</span>><span class="st">"00001"</span>},
<span class="st">"u"</span>=>{<span class="st">:$set=</span>>{<span class="st">:pop=</span>><span class="dv">4</span>}}, <span class="st">"multi"</span>=><span class="dv">false</span>, <span class="st">"upsert"</span>=><span class="dv">false</span>}], <span class="st">"writeConcern"</span>=>{<span class="st">:w=</span>><span class="dv">1</span>}, <span class="st">"ordered"</span>=><span class="dv">true</span>}
=> <span class="co">#<Mongo::Operation::Result:46208000 documents=[{"ok"=>1, "nModified"=>1, "n"=>1}]> </span>
> zip=<span class="dt">Zip</span>.find(<span class="st">"00001"</span>).population
getting zip <span class="dv">00001</span>
=> <span class="dv">4</span> </code></pre>
<h3 id="add-a-destroy-instance-method-to-remove-the-current-zip-from-the-database">Add a <code>destroy()</code> Instance Method to Remove the Current Zip from the Database</h3>
<p><code>destroy()</code> will delete the document from the database that is associated with the instance's <code>:id</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> destroy
<span class="dt">Rails</span>.logger.debug {<span class="st">"destroying </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st">"</span>}
<span class="dv">self</span>.class.collection
.find(_id<span class="st">:@id</span>)
.delete_one
<span class="kw">end</span></code></pre>
<p>Load an instance with the state of one of the cities and remove that city from the database.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> zip=<span class="dt">Zip</span>.find <span class="st">"00001"</span>
getting zip <span class="dv">00001</span>
zips_development.find | {<span class="st">"find"</span>=><span class="st">"zips"</span>, <span class="st">"filter"</span>=>{<span class="st">"_id"</span>=><span class="st">"00001"</span>},
<span class="st">"projection"</span>=>{<span class="st">"_id"</span>=><span class="dv">true</span>, <span class="st">"city"</span>=><span class="dv">true</span>, <span class="st">"state"</span>=><span class="dv">true</span>, <span class="st">"pop"</span>=><span class="dv">true</span>}}
=> <span class="co">#<Zip:0x0000000647cee8 @id="00001", @city="Fake City", @state="WY", @population=4> </span>
> zip.destroy
destroying <span class="dv">00001</span>: <span class="dt">Fake</span> <span class="dt">City</span>, <span class="dt">WY</span>, pop=<span class="dv">4</span>
zips_development.delete | {<span class="st">"delete"</span>=><span class="st">"zips"</span>,
<span class="st">"deletes"</span>=>[{<span class="st">"q"</span>=>{<span class="st">"_id"</span>=><span class="st">"00001"</span>}, <span class="st">"limit"</span>=><span class="dv">1</span>}], <span class="st">"writeConcern"</span>=>{<span class="st">"w"</span>=><span class="dv">1</span>}, <span class="st">"ordered"</span>=><span class="dv">true</span>}
=> <span class="co">#<Mongo::Operation::Result:52648160 documents=[{"ok"=>1, "n"=>1}]> </span>
> zip=<span class="dt">Zip</span>.find <span class="st">"00001"</span>
getting zip <span class="dv">00001</span>
=> <span class="dv">nil</span> </code></pre>
<h3 id="include-activemodelmodel-mixin-behavor">Include <code>ActiveModel::Model</code> Mixin Behavor</h3>
<p>We need to include the <code>ActiveModel::Model</code> mixin and override its <code>persisted?</code> implementation to simply return the result of whether a primary key has been assigned. JSON marshalling will also expect a <code>created_at</code> and <code>updated_at</code> by default.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">Zip</span>
include <span class="dt">ActiveModel</span>::<span class="dt">Model</span>
...
<span class="kw">def</span> persisted?
!<span class="ot">@id</span>.nil?
<span class="kw">end</span>
<span class="kw">def</span> created_at
<span class="dv">nil</span>
<span class="kw">end</span>
<span class="kw">def</span> updated_at
<span class="dv">nil</span>
<span class="kw">end</span></code></pre>
<h3 id="full-model-class">Full Model Class</h3>
<p>Our class should now be at a point where it can integrate with Rails as an official Model class -- with some help.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">Zip</span>
include <span class="dt">ActiveModel</span>::<span class="dt">Model</span>
<span class="ot">attr_accessor</span> <span class="st">:id</span>, <span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:population</span>
<span class="kw">def</span> to_s
<span class="st">"</span><span class="ot">#{@id}</span><span class="st">: </span><span class="ot">#{@city}</span><span class="st">, </span><span class="ot">#{@state}</span><span class="st">, pop=</span><span class="ot">#{@population}</span><span class="st">"</span>
<span class="kw">end</span>
<span class="co"># initialize from both a Mongo and Web hash</span>
<span class="kw">def</span> initialize(params={})
<span class="co">#switch between both internal and external views of id and population</span>
<span class="ot">@id</span>=params[<span class="st">:_id</span>].nil? ? params[<span class="st">:id</span>] : params[<span class="st">:_id</span>]
<span class="ot">@city</span>=params[<span class="st">:city</span>]
<span class="ot">@state</span>=params[<span class="st">:state</span>]
<span class="ot">@population</span>=params[<span class="st">:pop</span>].nil? ? params[<span class="st">:population</span>] : params[<span class="st">:pop</span>]
<span class="kw">end</span>
<span class="co"># tell Rails whether this instance is persisted</span>
<span class="kw">def</span> persisted?
!<span class="ot">@id</span>.nil?
<span class="kw">end</span>
<span class="kw">def</span> created_at
<span class="dv">nil</span>
<span class="kw">end</span>
<span class="kw">def</span> updated_at
<span class="dv">nil</span>
<span class="kw">end</span>
<span class="co"># convenience method for access to client in console</span>
<span class="kw">def</span> <span class="dv">self</span>.mongo_client
<span class="dt">Mongoid</span>::<span class="dt">Clients</span>.default
<span class="kw">end</span>
<span class="co"># convenience method for access to zips collection</span>
<span class="kw">def</span> <span class="dv">self</span>.collection
<span class="dv">self</span>.mongo_client[<span class="st">'zips'</span>]
<span class="kw">end</span>
<span class="co"># implement a find that returns a collection of document as hashes. </span>
<span class="co"># Use initialize(hash) to express individual documents as a class </span>
<span class="co"># instance. </span>
<span class="co"># * prototype - query example for value equality</span>
<span class="co"># * sort - hash expressing multi-term sort order</span>
<span class="co"># * offset - document to start results</span>
<span class="co"># * limit - number of documents to include</span>
<span class="kw">def</span> <span class="dv">self</span>.all(prototype={}, sort={<span class="st">:population=</span>><span class="dv">1</span>}, offset=<span class="dv">0</span>, limit=<span class="dv">100</span>)
<span class="co">#map internal :population term to :pop document term</span>
tmp = {} <span class="co">#hash needs to stay in stable order provided</span>
sort.each {|k,v|
k = k.to_sym==<span class="st">:population</span> ? <span class="st">:pop</span> : k.to_sym
tmp[k] = v <span class="kw">if</span> [<span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:pop</span>].include?(k)
}
sort=tmp
<span class="co">#convert to keys and then eliminate any properties not of interest</span>
prototype=prototype.symbolize_keys.slice(<span class="st">:city</span>, <span class="st">:state</span>) <span class="kw">if</span> !prototype.nil?
<span class="dt">Rails</span>.logger.debug {<span class="st">"getting all zips, prototype=</span><span class="ot">#{</span>prototype<span class="ot">}</span><span class="st">, sort=</span><span class="ot">#{</span>sort<span class="ot">}</span><span class="st">, offset=</span><span class="ot">#{</span>offset<span class="ot">}</span><span class="st">, limit=</span><span class="ot">#{</span>limit<span class="ot">}</span><span class="st">"</span>}
result=collection.find(prototype)
.projection({_id<span class="st">:true</span>, city<span class="st">:true</span>, state<span class="st">:true</span>, pop<span class="st">:true</span>})
.sort(sort)
.skip(offset)
result=result.limit(limit) <span class="kw">if</span> !limit.nil?
<span class="kw">return</span> result
<span class="kw">end</span>
<span class="co"># locate a specific document. Use initialize(hash) on the result to </span>
<span class="co"># get in class instance form</span>
<span class="kw">def</span> <span class="dv">self</span>.find id
<span class="dt">Rails</span>.logger.debug {<span class="st">"getting zip </span><span class="ot">#{</span>id<span class="ot">}</span><span class="st">"</span>}
doc=collection.find(<span class="st">:_id=</span>>id)
.projection({_id<span class="st">:true</span>, city<span class="st">:true</span>, state<span class="st">:true</span>, pop<span class="st">:true</span>})
.first
<span class="kw">return</span> doc.nil? ? <span class="dv">nil</span> : <span class="dt">Zip</span>.new(doc)
<span class="kw">end</span>
<span class="co"># create a new document using the current instance</span>
<span class="kw">def</span> save
<span class="dt">Rails</span>.logger.debug {<span class="st">"saving </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st">"</span>}
<span class="dv">self</span>.class.collection
.insert_one(_id<span class="st">:@id</span>, city<span class="st">:@city</span>, state<span class="st">:@state</span>, pop<span class="st">:@population</span>)
<span class="kw">end</span>
<span class="co"># update the values for this instance</span>
<span class="kw">def</span> update(updates)
<span class="dt">Rails</span>.logger.debug {<span class="st">"updating </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st"> with </span><span class="ot">#{</span>updates<span class="ot">}</span><span class="st">"</span>}
<span class="co">#map internal :population term to :pop document term</span>
updates[<span class="st">:pop</span>]=updates[<span class="st">:population</span>] <span class="kw">if</span> !updates[<span class="st">:population</span>].nil?
updates.slice!(<span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:pop</span>) <span class="kw">if</span> !updates.nil?
<span class="dv">self</span>.class.collection
.find(_id<span class="st">:@id</span>)
.update_one(<span class="st">:$set=</span>>updates)
<span class="kw">end</span>
<span class="co"># remove the document associated with this instance form the DB</span>
<span class="kw">def</span> destroy
<span class="dt">Rails</span>.logger.debug {<span class="st">"destroying </span><span class="ot">#{</span><span class="dv">self</span><span class="ot">}</span><span class="st">"</span>}
<span class="dv">self</span>.class.collection
.find(_id<span class="st">:@id</span>)
.delete_one
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<h2 id="create-controller-and-view">Create Controller and View</h2>
<p>Generate the controller and view using a scaffold command that does not create a model class.</p>
<pre class="shell"><code>$ 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.jbuilder</code></pre>
<p>Verify the route to the new controller is in place in <code>config/routes.rb</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="dt">Rails</span>.application.routes.draw <span class="kw">do</span>
resources <span class="st">:zips</span></code></pre>
<p>Access the new page and observe an error between what was returned by the Model (a hash with an <code>:_id</code> key) and what is required by the view (an instance of a class with the id() method).</p>
<pre><code>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></code></pre>
<p>Add the following helper method to the <code>app/helpers/zips_helper.rb</code> to convert a Mongo document to a Ruby class instance. We left it as a document so the <code>all()</code> method did not have to EAGERly access every document in the result set before it was know it would be used.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">module</span> <span class="dt">ZipsHelper</span>
<span class="kw">def</span> toZip(value)
<span class="co">#change value to a Zip if not already a Zip </span>
<span class="kw">return</span> value.is_a?(<span class="dt">Zip</span>) ? value : <span class="dt">Zip</span>.new(value)
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<p>Add a call to the helper method in <code>app/views/zips/index.html.erb</code></p>
<pre class="sourceCode html"><code class="sourceCode html"> <span class="er"><</span>% @zips.each do |zip| %>
<span class="er"><</span>% zip=toZip(zip) %></code></pre>
<p>Not there is a similar issue with the JSON view as well</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">http:<span class="ot">//</span>localhost:<span class="dv">3000</span>/zips.json
key <span class="kw">not</span> found: <span class="st">:id</span>
json.array!(<span class="ot">@zips</span>) <span class="kw">do</span> |zip|
json.extract! zip, <span class="st">:id</span>, <span class="st">:id</span>, <span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:population</span>
json.url zip_url(zip, format: <span class="st">:json</span>)
<span class="kw">end</span></code></pre>
<p>Fix the JSON view error by calling the helper method in <code>app/helpers/index.json.builder</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">json.array!(<span class="ot">@zips</span>) <span class="kw">do</span> |zip|
zip=toZip(zip)
json.extract! zip, <span class="st">:id</span>, <span class="st">:id</span>, <span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:population</span>
json.url zip_url(zip, format: <span class="st">:json</span>)
<span class="kw">end</span></code></pre>
<h2 id="test-drive">Test Drive</h2>
<p>In our <code>ZipsController</code>, prior to a <code>:show</code>, <code>:edit</code>, <code>:update</code>, or <code>:destroy</code> action being executed, we will first invoke the <code>set_zip()</code> method to retreive the specific <code>Zip</code> by <code>id</code>. The generated helper comes ready to call <code>Zip.find</code> and expect to get an instance back.</p>
<p>The <code>zip_params()</code> method restricts mass assignments for <code>:zip</code> parameters to the fields of <code>:id</code>, <code>:city</code>, <code>:state</code>, and <code>:populaton</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">ZipsController</span> < <span class="dt">ApplicationController</span>
before_action <span class="st">:set_zip</span>, only: [<span class="st">:show</span>, <span class="st">:edit</span>, <span class="st">:update</span>, <span class="st">:destroy</span>]
<span class="kw">private</span>
<span class="co"># Use callbacks to share common setup or constraints between actions.</span>
<span class="kw">def</span> set_zip
<span class="ot">@zip</span> = <span class="dt">Zip</span>.find(params[<span class="st">:id</span>])
<span class="kw">end</span>
<span class="co"># Never trust parameters from the scary internet, only allow the white list through.</span>
<span class="kw">def</span> zip_params
params.require(<span class="st">:zip</span>).permit(<span class="st">:id</span>, <span class="st">:city</span>, <span class="st">:state</span>, <span class="st">:population</span>)
<span class="kw">end</span></code></pre>
<h3 id="index">Index</h3>
<p><code>index()</code> retrieves all the <code>Zips</code>. The generated action method comes ready to call <code>Zip.all</code>.</p>
<pre><code>http://localhost:3000/zips
http://localhost:3000/zips.json
def index
@zips = Zip.all
end</code></pre>
<h3 id="show">Show</h3>
<p><code>show()</code> retrieves a specific <code>Zip</code> based upon its <code>id</code>. The generated action method is fully implemented by the generated <code>set_zip</code> helper method.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="co">#GET /zips/{id}</span>
<span class="co">#GET /zips/{id}.json</span>
before_action <span class="st">:set_zip</span>, only: [<span class="st">:show</span>, <span class="st">:edit</span>, <span class="st">:update</span>, <span class="st">:destroy</span>]
<span class="kw">def</span> set_zip
<span class="ot">@zip</span> = <span class="dt">Zip</span>.find(params[<span class="st">:id</span>])
<span class="kw">end</span>
<span class="kw">def</span> show
<span class="kw">end</span></code></pre>
<h3 id="new-create">New + Create</h3>
<p><code>new()</code> returns an initial prototype to the form to create a new <code>Zip</code>.</p>
<p><code>create()</code> accepts the results and creates a new <code>Zip</code> instance in the database.</p>
<p>Both of these generated action methods come ready to call <code>Zip.new</code>, which uses the <code>initialize</code> method. The generated create action also comes ready to invoke <code>save</code> on the <code>Zip</code> instance.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="co">#POST /zips/new</span>
<span class="kw">def</span> new
<span class="ot">@zip</span> = <span class="dt">Zip</span>.new
<span class="kw">end</span>
<span class="co">#POST /zips</span>
<span class="kw">def</span> create
<span class="ot">@zip</span> = <span class="dt">Zip</span>.new(zip_params)
respond_to <span class="kw">do</span> |format|
<span class="kw">if</span> <span class="ot">@zip</span>.save
format.html { redirect_to <span class="ot">@zip</span>, notice: <span class="st">'Zip was successfully created.'</span> }
format.json { render <span class="st">:show</span>, status: <span class="st">:created</span>, location: <span class="ot">@zip</span> }
<span class="kw">else</span>
format.html { render <span class="st">:new</span> }
format.json { render json: <span class="ot">@zip</span>.errors, status: <span class="st">:unprocessable_entity</span> }
<span class="kw">end</span>
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<h3 id="edit-update">Edit + Update</h3>
<p><code>edit()</code> retrieves the instance from the database based upon its <code>id</code>.</p>
<p><code>update()</code> applies the changes to the retrieved instance provided by <code>edit()</code>.</p>
<p>Both generated action methods rely on the <code>before_action</code> and the generated <code>update</code> action also comes ready to call <code>update</code> on the <code>Zip</code> instance.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">http:<span class="ot">//</span>localhost:<span class="dv">3000</span>/zips/<span class="dv">00002</span>/edit
<span class="co">#GET /zips/{id}</span>
before_action <span class="st">:set_zip</span>, only: [<span class="st">:show</span>, <span class="st">:edit</span>, <span class="st">:update</span>, <span class="st">:destroy</span>]
<span class="kw">def</span> set_zip
<span class="ot">@zip</span> = <span class="dt">Zip</span>.find(params[<span class="st">:id</span>])
<span class="kw">end</span>
<span class="kw">def</span> edit
<span class="kw">end</span>
<span class="co">#PUT /zips/{id}</span>
<span class="kw">def</span> update
respond_to <span class="kw">do</span> |format|
<span class="kw">if</span> <span class="ot">@zip</span>.update(zip_params)
format.html { redirect_to <span class="ot">@zip</span>, notice: <span class="st">'Zip was successfully updated.'</span> }
format.json { render <span class="st">:show</span>, status: <span class="st">:ok</span>, location: <span class="ot">@zip</span> }
<span class="kw">else</span>
format.html { render <span class="st">:edit</span> }
format.json { render json: <span class="ot">@zip</span>.errors, status: <span class="st">:unprocessable_entity</span> }
<span class="kw">end</span>
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<h3 id="destroy">Destroy</h3>
<p><code>destroy()</code> removes a specific <code>Zip</code> instance based upon its <code>id</code>. The generated action method comes ready to call <code>destroy</code> on the <code>Zip</code> instance.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="co">#DELETE /zips/{id}</span>
<span class="kw">def</span> destroy
<span class="ot">@zip</span>.destroy
respond_to <span class="kw">do</span> |format|
format.html { redirect_to zips_url, notice: <span class="st">'Zip was successfully destroyed.'</span> }
format.json { head <span class="st">:no_content</span> }
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<h3 id="root-application">Root Application</h3>
<p>Add a second line to <code>config/routes.rb</code> to make zips the root application.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="dt">Rails</span>.application.routes.draw <span class="kw">do</span>
root <span class="st">'zips#index'</span></code></pre>
<h2 id="add-pagination">Add <a href="https://github.com/mislav/will_paginate/wiki">Pagination</a></h2>
<h3 id="add-the-will_paginate-to-the-gemfile">Add the <code>will_paginate</code> to the Gemfile</h3>
<pre class="sourceCode ruby"><code class="sourceCode ruby">gem <span class="st">'will_paginate'</span>, <span class="st">'~> 3.0.7'</span></code></pre>
<h3 id="execute-bundle-to-install-the-gem">Execute <code>bundle</code> to install the Gem</h3>
<pre class="shell"><code>bundle</code></pre>
<h3 id="add-the-will_paginate-command-to-the-view">Add the <code>will_paginate</code> Command to the View</h3>
<p>This command will add page properties to the displayed view and controls advance paging. Specifically, it can pass <code>:page</code> as a number >= 1. <code>will_paginate</code> also uses <code>:per_page</code> to express the row limit for a single page.</p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><table></span>
...
<span class="kw"><tbody></span>
<span class="er"><</span>% @zips.each do |zip| %>
<span class="er"><</span>% zip=toZip(zip) %>
<span class="kw"><tr></span>
<span class="kw"><td></span><span class="er"><</span>%= zip.id %><span class="kw"></td></span>
...
<span class="kw"></tr></span>
<span class="er"><</span>% end %>
<span class="kw"></tbody></span>
<span class="kw"></table></span>
<span class="er"><</span>%= will_paginate @zips %></code></pre>
<h3 id="add-will_paginate-support-for-paging-in-the-controller">Add <code>will_paginate</code> Support for Paging in the Controller</h3>
<p>The controller is passing a controlled set of parameters to the <code>Model.paginate</code> call. The page number is currently the only value passed but <code>:per_page</code> could be specified here as well.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> index
<span class="co">#@zips = Zip.all</span>
<span class="ot">@zips</span> = <span class="dt">Zip</span>.paginate(<span class="st">:page</span> => params[<span class="st">:page</span>])
<span class="kw">end</span> </code></pre>
<h3 id="add-will_paginate-support-for-paging-in-the-model">Add <code>will_paginate</code> Support for Paging in the Model</h3>
<p>This method implements a facade around the <code>all()</code> method by translating the <code>will_paginate</code> inputs into <code>all()</code> query inputs and converts the document array results into a <code>will_paginate</code> result that contains such things as total number of documents.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> <span class="dv">self</span>.paginate(params)
<span class="dt">Rails</span>.logger.debug(<span class="st">"paginate(</span><span class="ot">#{</span>params<span class="ot">}</span><span class="st">)"</span>)
page=(params[<span class="st">:page</span>] ||= <span class="dv">1</span>).to_i
limit=(params[<span class="st">:per_page</span>] ||= <span class="dv">30</span>).to_i
offset=(page<span class="dv">-1</span>)*limit
<span class="co">#get the associated page of Zips -- eagerly convert doc to Zip</span>
zips=[]
all({}, {}, offset, limit).each <span class="kw">do</span> |doc|
zips << <span class="dt">Zip</span>.new(doc)
<span class="kw">end</span>
<span class="co">#get a count of all documents in the collection</span>
total=all({}, {}, <span class="dv">0</span>, <span class="dv">1</span>).count
<span class="dt">WillPaginate</span>::<span class="dt">Collection</span>.create(page, limit, total) <span class="kw">do</span> |pager|
pager.replace(zips)
<span class="kw">end</span>
<span class="kw">end</span></code></pre>
<p><a href="http://pathfindersoftware.com/2008/06/how-to-use-will_paginate-with-non-activerecord-collectionarray/">A reference on how to use will_paginate</a></p>
<h2 id="quick-test-drive">Quick Test Drive</h2>
<p>As a quick test to verify our current <code>will_paginate</code> implementation, the following URL should land us on the 3rd page of "Listing Zips"</p>
<p><code>http://localhost:3000/?page=3</code></p>
<h3 id="add-selection-criteria-and-ordering">Add Selection Criteria and Ordering</h3>
<p>Given the following URL:</p>
<p><code>http://localhost:3000/?page=38&per_page=10&sort=population:-1,city:1&state=MD</code></p>
<p>Lets implement additional functionality that will allow our application to:</p>
<ul>
<li>Specify a particular page number: <code>page=38</code></li>
<li>Define a row limit per page: <code>per_page=10</code></li>
<li>Order the results by population DESC, city ASC: <code>sort=population:-1,city:1</code></li>
<li>Define zips for a particular state: <code>state=MD</code></li>
</ul>
<h4 id="create-a-helper-method-in-the-controller-to-convert-the-sort-query-param-to-a-mongodb-query-sort-hash">Create a Helper Method in the Controller to Convert the Sort Query Param to a MongoDB Query Sort Hash</h4>
<p><code>app/controller/zips_controller.rb</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">private</span>
<span class="co">#create a hash sort spec from query param</span>
<span class="co">#sort=state:1,city,population:-1</span>
<span class="co">#{state:1, city:1, population:-1}</span>
<span class="kw">def</span> get_sort_hash(sort)
order={}
<span class="kw">if</span> (!sort.nil?)
sort.split(<span class="st">","</span>).each <span class="kw">do</span> |term|
args=term.split(<span class="st">":"</span>)
dir = args.length<<span class="dv">2</span> || args[<span class="dv">1</span>].to_i >= <span class="dv">0</span> ? <span class="dv">1</span> : -<span class="dv">1</span>
order[args[<span class="dv">0</span>]] = dir
<span class="kw">end</span>
<span class="kw">end</span>
<span class="kw">return</span> order
<span class="kw">end</span></code></pre>
<h4 id="update-the-controller-method-to-pass-the-query-and-sort-terms-into-the-will_paginate-call">Update the Controller Method to Pass the Query and Sort Terms into the will_paginate Call</h4>
<p>This passes right to our <code>Model.paginate</code> call where we can add a small amount of processing to pass it through to the <code>all()</code> method.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> index
<span class="co">#@zips = Zip.all</span>
<span class="co">#@zips = Zip.paginate(params)</span>
args=params.clone <span class="co">#update a clone of params</span>
args[<span class="st">:sort</span>]=get_sort_hash(args[<span class="st">:sort</span>]) <span class="co">#replace sort with hash</span>
<span class="ot">@zips</span> = <span class="dt">Zip</span>.paginate(args)
<span class="kw">end</span></code></pre>
<h4 id="update-the-model-method-to-pass-the-query-and-sort-terms-into-all">Update the Model Method to Pass the Query and Sort Terms into <code>all()</code></h4>
<p><code>app/models/zip.rb</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> <span class="dv">self</span>.paginate(params)
...
sort=params[<span class="st">:sort</span>] ||= {}
...
all(params, sort, offset, limit).each <span class="kw">do</span> |doc|
...
total=all(params, sort, <span class="dv">0</span>, <span class="dv">1</span>).count
...
<span class="kw">end</span></code></pre>
<h3 id="final-test-drive">Final Test Drive</h3>
<p>With our selection criteria and ordering logic now in place, the below URL:</p>
<p><code>http://localhost:3000/?page=38&per_page=10&sort=population:-1,city:1&state=MD</code></p>
<p>should render us results similiar to this:</p>
<h3 id="listing-zips">Listing Zips</h3>
<table>
<thead>
<tr class="header">
<th align="left">Id</th>
<th align="left">City</th>
<th align="left">State</th>
<th align="left">Population</th>
<th align="left"></th>
<th align="left"></th>
<th align="left"></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td align="left">21522</td>
<td align="left">BITTINGER</td>
<td align="left">MD</td>
<td align="left">479</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="even">
<td align="left">21156</td>
<td align="left">Upper Falls</td>
<td align="left">MD</td>
<td align="left">464</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="odd">
<td align="left">20632</td>
<td align="left">FAULKNER</td>
<td align="left">MD</td>
<td align="left">459</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="even">
<td align="left">21677</td>
<td align="left">WOOLFORD</td>
<td align="left">MD</td>
<td align="left">459</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="odd">
<td align="left">21816</td>
<td align="left">CHANCE</td>
<td align="left">MD</td>
<td align="left">415</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="even">
<td align="left">20630</td>
<td align="left">DRAYDEN</td>
<td align="left">MD</td>
<td align="left">413</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="odd">
<td align="left">20779</td>
<td align="left">TRACYS LANDING</td>
<td align="left">MD</td>
<td align="left">413</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="even">
<td align="left">20615</td>
<td align="left">BROOMES ISLAND</td>
<td align="left">MD</td>
<td align="left">404</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="odd">
<td align="left">21672</td>
<td align="left">TODDVILE</td>
<td align="left">MD</td>
<td align="left">361</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
<tr class="even">
<td align="left">21840</td>
<td align="left">NANTICOKE</td>
<td align="left">MD</td>
<td align="left">358</td>
<td align="left">Show</td>
<td align="left">Edit</td>
<td align="left">Destroy</td>
</tr>
</tbody>
</table>
<p><code><-- Previous 1 2 ... 34 35 36 37 38 39 40 41 42 Next --></code></p>
<p>If you flip the value of <code>city:1</code> from <code>1</code> to <code>-1</code>, should will see the ordering of <code>DRAYDEN</code> and <code>TRACYS LANDING</code> switch places.</p>
<h2 id="heroku-deployment">Heroku Deployment</h2>
<h3 id="setup-database-on-mongolabs">Setup Database on MongoLabs</h3>
<ol style="list-style-type: decimal">
<li><p>Create a <a href="https://mongolab.com/"><code>MongoLabs Account</code></a></p>
<pre><code>https://mongolab.com/home</code></pre></li>
<li><p>Create a (Free Sandbox) Database on MongoLabs</p>
<ul>
<li>From your mongolab home page, select <code>Create New</code> MongoDB Deployments</li>
<li>For <code>Cloud Provider:</code> select <code>Amazon Web Services</code>
<ul>
<li>On the <code>Location:</code> Pull Down Menu, select the Region that is geographically closest to you<br /></li>
</ul></li>
<li>Under Plan: select the <code>Single-node</code> Option
<ul>
<li>Select the Free <code>Sanbox</code> option under <code>Standard Line</code></li>
</ul></li>
<li>Leave the <code>High Storage Line</code> options blank</li>
<li>In the <code>Database name:</code> input field, supply a name (i.e., zips_production)</li>
<li>Verify the <code>Price:</code> field calculator is <code>$0 / month</code></li>
<li>Select the <code>Create new MongoDB deployment</code> button</li>
<li>On your mongolab home page, you should now see your database (i.e., zips_production) listed</li>
<li>Now select your newly created MongoDB deployment</li>
<li><p>The following URL template should be displayed with the details pertaining to your deployment</p>
<pre><code>To connect using a driver via the standard MongoDB URI:
mongodb://<dbuser>:<dbpass>@<dbhost>/<dbname></code></pre></li>
</ul></li>
<li><p>Create a Database User and Password on MongoLabs</p>
<ul>
<li>Select the <code>Users</code> Menu</li>
<li>Select the <code>Add database user</code> button</li>
<li>In the <code>Add new database user</code> form, supply a username and password:
<ul>
<li><code><dbuser></code></li>
<li><code><dbpass></code></li>
<li>then select <code>Create</code></li>
</ul></li>
</ul></li>
<li><p>Import <a href="http://media.mongodb.org/zips.json"><code>zips.json</code></a> from MongoDB using the database and user account created above</p>
<pre class="shell"><code>$ 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######.mongolab.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</code></pre></li>
</ol>
<h3 id="setup-application-on-heroku">Setup Application on Heroku</h3>
<ol style="list-style-type: decimal">
<li><p>Create a <a href="https://heroku.com"><code>Heroku Account</code></a></p>
<ul>
<li>If not yet installed, download and install the <a href="https://toolbelt.heroku.com/">Heroku Toolbelt</a>.<br /></li>
<li>This client CLI will be used in later steps that rely on <code>heroku</code> commands</li>
</ul></li>
<li><p>Register your application with Heroku by changing to the directory with a git repository and invoking <code>heroku apps:create (appname)</code>.</p>
<p><strong>Note that your application must be in the root directory of the development folder hosting the git repository.</strong></p>
<pre><code>$ 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 added</code></pre>
<p>This will add an additional remote to your git repository.</p>
<pre class="shell"><code>$ git remote --verbose
heroku https://git.heroku.com/appname.git (fetch)
heroku https://git.heroku.com/appname.git (push)
...</code></pre></li>
<li><p>Add a <code>MONGOLAB_URI</code> environment variable where <code>dbhost</code> is both host and port# concatenated together, separated by a ":" (host:port) .</p>
<pre class="shell"><code>$ heroku config:add MONGOLAB_URI=mongodb://dbuser:dbpass@dbhost/dbname</code></pre></li>
<li><p>Add a <code>production</code> profile to the <code>config/mongoid.yml</code> file. The following <a href="https://devcenter.heroku.com/articles/mongolab#connecting-to-existing-mongolab-deployments-from-heroku"><code>Mongoid connection information</code></a> was provided by the <code>MongoLab</code> page on the <code>Heroku</code> Dev Center page.</p>
<pre class="sourceCode yaml"><code class="sourceCode yaml"><span class="fu">production:</span>
<span class="fu">clients:</span>
<span class="fu">default:</span>
<span class="fu">uri:</span> <%= ENV['MONGOLAB_URI'] %>
<span class="fu">options:</span>
<span class="fu">connect_timeout:</span> 15</code></pre></li>
<li><p>Update the <code>Gemfile</code> so that Heroku will accept and deploy our application.</p>
<p>Restrict the <code>sqlite</code> gem in <code>Gemfile</code> to the development profile. Heroku does not support <code>sqlite</code> and this application does not use an <code>RDBMS</code>. However, this gem was put there by by <code>rails new</code> by default and required to stick around because we have not removed ActiveRecord from the application.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">gem <span class="st">'sqlite3'</span>, group: <span class="st">:development</span></code></pre>
<p>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.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">group <span class="st">:production</span> <span class="kw">do</span>
<span class="co">#use postgres on heroku</span>
gem <span class="st">'pg'</span>
gem <span class="st">'rails_12factor'</span>
<span class="kw">end</span> </code></pre>
<p>Be sure to run bundle when complete and check the Gemfile.lock file into git.</p>
<pre class="shell"><code>$ bundle</code></pre></li>
<li><p>Commit changes to application</p>
<pre class="shell"><code>$ git commit -am "ready for heroku deploy"</code></pre></li>
<li><p>Deploy application</p>
<pre class="shell"><code>$ git push heroku master</code></pre></li>
</ol>
<h3 id="access-application">Access Application</h3>
<ol style="list-style-type: decimal">
<li><p>Access URL</p>
<pre class="url"><code>http://appname.herokuapp.com</code></pre></li>
<li><p>Access logs</p>
<pre class="shell"><code>$ heroku logs</code></pre></li>
</ol>