-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathREADME.html
436 lines (432 loc) · 38.6 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
<h1 id="example-gridfs-rails-file-server">Example: GridFS Rails File Server</h1>
<p>In this example, we will create a bare bones, web-based file server that we can upload, store, get, and download contents from. The application will be backed by GridFS. Access to GridFS will be done through a model class implemented to work with the Rails scaffold. Much of the model will be assembled and tested using <code>rails console</code> prior to addig the controller and view.</p>
<h2 id="highlights">Highlights</h2>
<p><a href="./index.jpg">index page</a></p>
<p><a href="./show.jpg">show page</a></p>
<ol style="list-style-type: decimal">
<li><p>Files are uploaded using the browser and the <code>f.file_field</code> option <code>(app/views/grid_fs_files/_form.html.erb )</code>.</p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :contents %><span class="kw"><br></span>
<span class="er"><</span>%= f.file_field :contents %>
<span class="kw"></div></span></code></pre></li>
<li><p>The object type supplied for <code>contents</code> by Rails is an <code>ActionDispatch::Http::UploadedFile</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="co">#<ActionDispatch::Http::UploadedFile:0x00000005597018></span></code></pre></li>
<li><p>The <code>UploadedFile</code> can be read directly into the <code>Grid::File</code> with the hash of file description properties. The root level keys in the hash are standard within GridFS. The keys below <code>metadata</code> are user-defined. Note that GridFS uses <code>snake_case</code> keys in the <code>Grid::File</code> object but uses <code>camelCase</code> in the hash info interface we will see later.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">description = {<span class="st">:filename=</span>><span class="ot">@filename</span>,
<span class="st">:content_type=</span>><span class="ot">@contentType</span>,
<span class="st">:metadata</span> => {<span class="st">:author</span> => <span class="ot">@author</span>, <span class="st">:topic</span> => <span class="ot">@topic</span>}}
grid_file = <span class="dt">Mongo</span>::<span class="dt">Grid</span>::<span class="dt">File</span>.new(<span class="ot">@contents</span>.read, description )
id=<span class="dv">self</span>.class.mongo_client.database.fs.insert_one(grid_file)
<span class="ot">@id</span>=id.to_s</code></pre></li>
<li><p>The file is accessed by a URI that can be defined using an <code>img</code> tag (<code>app/views/grid_fs_files/show.html.erb</code>).</p>
<pre><code><p>
<strong>Contents:</strong>
<img height="500px" width="650px" src= <%= contents_path("#{@grid_fs_file.id}")%>/>
</p></code></pre></li>
<li><p>This URI is defined using a <code>GET</code> in the <code>routes.rb</code> file mapped to the controller <code>contents</code> action. (<code>config/routes.rb</code>). By defining this as <code>contents</code> resource, we get the helper method <code>contents_path</code> used above.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">get <span class="st">'/grid_fs_files/contents/:id/'</span>, to: <span class="st">'grid_fs_files#contents'</span>, as: <span class="st">'contents'</span></code></pre></li>
<li><p>The controller method accesses the data from the contents attribute and sends this back to the web caller with a few HTTP properties. For example, by supplying <code>filename</code>, the file will default to the name provided in the model.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">GridFsFilesController</span> < <span class="dt">ApplicationController</span>
before_action <span class="st">:set_grid_fs_file</span>, only: [<span class="st">:show</span>, <span class="st">:edit</span>, <span class="st">:update</span>, <span class="st">:destroy</span>, <span class="st">:contents</span>]
<span class="kw">def</span> contents
send_data <span class="ot">@grid_fs_file</span>.contents,
{filename: <span class="ot">@grid_fs_file</span>.filename,
type: <span class="ot">@grid_fs_file</span>.contentType,
disposition: <span class="st">'inline'</span>}
<span class="kw">end</span></code></pre></li>
<li><p>The getter for <code>contents</code> is provided by a custom implementation that locates the GridFS content by <code>id</code> and returns a buffer with the data from each chunk.</p></li>
</ol>
<p>def contents f=self.class.mongo_client.database.fs.find_one({:<em>id=>BSON::ObjectId.from</em>string(<span class="citation">@id</span>)}) buffer = "" f.chunks.reduce([]) { |x,chunk| buffer << chunk.data.data } return buffer end</p>
<h2 id="infrastructure">Infrastructure</h2>
<p>This section will lightly show the steps required to get the supporting part of the demo in place.</p>
<h3 id="create-the-rails-application-and-setup-mongodb-connection">Create The Rails Application and Setup MongoDB Connection</h3>
<ol style="list-style-type: decimal">
<li><p>Create the application</p>
<pre class="shell"><code>$ rails new gridfsfiles
$ cd gridfsfiles</code></pre></li>
<li><p>Add gems to <code>Gemfile</code></p>
<p>Mongoid is required for the connection management. It will automatically bring in mongo (MongDB Ruby Driver) -- which is still the focus of this lesson. However, you can specify both using the following.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">gem <span class="st">'mongo'</span>, <span class="st">'~> 2.1.0'</span>
gem <span class="st">'mongoid'</span>, <span class="st">'~> 5.0.0'</span></code></pre>
<p>This brought in the following versions when the example was written.</p>
<pre class="shell"><code>$bundle
Using mongo 2.1.2
Using mongoid 5.0.1</code></pre></li>
<li><p>Create the Mongoid Connection Configuration File</p>
<pre class="shell"><code>$ rails g mongoid:config
create config/mongoid.yml</code></pre>
<p>This creates a configuration with usable defaults for the <code>development</code> (and <code>test</code>) profile.</p>
<pre class="shell"><code>$ egrep -v '\#|^$' config/mongoid.yml
development:
clients:
default:
database: gridfsfiles_development
hosts:
- localhost:27017
options:
options:
...</code></pre></li>
<li><p>Load the Mongoid Configuration File into Rails Application</p>
<p><code>config/application.rb</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">module</span> <span class="dt">Gridfsfiles</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="kw">end</span>
<span class="kw">end</span></code></pre></li>
</ol>
<h2 id="create-gridfs-model-class-for-file-content">Create GridFS Model Class for File Content</h2>
<h3 id="create-gridfs-model-class">Create GridFS Model Class</h3>
<ol style="list-style-type: decimal">
<li><p>Create a GridFsFile model class to implement interactions between our application and GridFS. Start with the core properties required by the Rails scaffold like we saw with the <code>zips</code> application.</p>
<p><code>app/models/grid_fs_file.rb</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">GridFsFile</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="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="kw">end</span></code></pre></li>
<li><p>Create some file attributes to track for the contents. Start by locating properties we get from GridFS. Know that the metadata property is user-defined.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> pp c.database.fs.find.first
{<span class="st">"_id"</span>=><span class="dt">BSON</span>::<span class="dt">ObjectId</span>(<span class="st">'5642f149e301d09ce9000009'</span>),
<span class="st">"chunkSize"</span>=><span class="dv">261120</span>,
<span class="st">"uploadDate"</span>=><span class="dv">2015-11-11</span> <span class="bn">07</span>:<span class="dv">41</span>:<span class="dv">50</span> <span class="dt">UTC</span>,
<span class="st">"contentType"</span>=><span class="st">"image/jpeg"</span>,
<span class="st">"filename"</span>=><span class="st">"myfile.jpg"</span>,
<span class="st">"metadata"</span>=>{<span class="st">"author"</span>=><span class="st">"kiran"</span>, <span class="st">"topic"</span>=><span class="st">"nice spot"</span>},
<span class="st">"length"</span>=><span class="dv">307797</span>,
<span class="st">"md5"</span>=><span class="st">"3468ca1c23cc13ac6af493c4642cc72a"</span>}</code></pre>
<p>Define the above GridFS properties as attributes of the model class. Lets use the same camel case as GridFS to keep things consistent between Rails and GridFS hashes as possible. Add in metadata properties of <code>author</code> and <code>topic</code> as an example of tracking additional data. We also have refined the attributes into read/write, read-only, and write-only accesses. The GridFS descriptive information -- including the metadata we define -- is updatable at any time. <code>id</code>, <code>chunkSize</code>, <code>length</code>, and <code>md5</code> are all internally generated so we just define getters for those. <code>contents</code> is special. We will define a custom getter for it shortly.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">GridFsFile</span>
include <span class="dt">ActiveModel</span>::<span class="dt">Model</span>
<span class="ot">attr_accessor</span> <span class="st">:contentType</span>, <span class="st">:filename</span>, <span class="st">:author</span>, <span class="st">:topic</span>
<span class="ot">attr_writer</span> <span class="st">:contents</span>
<span class="ot">attr_reader</span> <span class="st">:id</span>, <span class="st">:uploadDate</span>, <span class="st">:chunkSize</span>, <span class="st">:length</span>, <span class="st">:md5</span></code></pre>
<p>Define an <code>initialize</code> method from a hash that can process hash keys produced by GridFS and Rails. Remember that MongoDB uses ':_id<code>for its primary key and Rails scaffold expects to use</code>:id'. Note too that since our custom <code>author</code> and <code>topic</code> fields are scoped below the GridFS <code>metadata</code> property, we can leverage the same <code>id</code> parameter test to determine whether we are representing this internally or externally.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> initialize(params={})
<span class="kw">if</span> params[<span class="st">:_id</span>] <span class="co">#hash came from GridFS</span>
<span class="ot">@id</span>=params[<span class="st">:_id</span>].to_s
<span class="ot">@author</span>=params[<span class="st">:metadata</span>].nil? ? <span class="dv">nil</span> : params[<span class="st">:metadata</span>][<span class="st">:author</span>]
<span class="ot">@topic</span>=params[<span class="st">:metadata</span>].nil? ? <span class="dv">nil</span> : params[<span class="st">:metadata</span>][<span class="st">:topic</span>]
<span class="kw">else</span> <span class="co">#assume hash came from Rails</span>
<span class="ot">@id</span>=params[<span class="st">:id</span>]
<span class="ot">@author</span>=params[<span class="st">:author</span>]
<span class="ot">@topic</span>=params[<span class="st">:topic</span>]
<span class="kw">end</span>
<span class="ot">@chunkSize</span>=params[<span class="st">:chunkSize</span>]
<span class="ot">@uploadDate</span>=params[<span class="st">:uploadDate</span>]
<span class="ot">@contentType</span>=params[<span class="st">:contentType</span>]
<span class="ot">@filename</span>=params[<span class="st">:filename</span>]
<span class="ot">@length</span>=params[<span class="st">:length</span>]
<span class="ot">@md5</span>=params[<span class="st">:md5</span>]
<span class="ot">@contents</span>=params[<span class="st">:contents</span>]
<span class="kw">end</span></code></pre></li>
</ol>
<h3 id="add-mongodb-connection">Add MongoDB Connection</h3>
<pre><code>```ruby
def self.mongo_client
@@db ||= Mongoid::Clients.default
end
```</code></pre>
<h3 id="add-save-capability">Add Save Capability</h3>
<ol style="list-style-type: decimal">
<li>Add an instance method to save the current instance.
<ul>
<li>the file data will from from an IO object stored in the <code>contents</code> attribute</li>
<li>an optional description is populate with file info, includig user-defined metadata</li>
<li>the <code>Grid::File</code> is inserted into GridFS and a primary key is returned</li>
<li>Note that the optional description takes a snake_case <code>content_type</code>, rather than the camelCase used in the upcoming find results.</li>
</ul>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> save
description = {}
description[<span class="st">:filename</span>]=<span class="ot">@filename</span> <span class="kw">if</span> !<span class="ot">@filename</span>.nil?
description[<span class="st">:content_type</span>]=<span class="ot">@contentType</span> <span class="kw">if</span> !<span class="ot">@contentType</span>.nil?
<span class="kw">if</span> <span class="ot">@author</span> || <span class="ot">@topic</span>
description[<span class="st">:metadata</span>] = {}
description[<span class="st">:metadata</span>][<span class="st">:author</span>]=<span class="ot">@author</span> <span class="kw">if</span> !<span class="ot">@author</span>.nil?
description[<span class="st">:metadata</span>][<span class="st">:topic</span>]=<span class="ot">@topic</span> <span class="kw">if</span> !<span class="ot">@topic</span>.nil?
<span class="kw">end</span>
<span class="kw">if</span> <span class="ot">@contents</span>
grid_file = <span class="dt">Mongo</span>::<span class="dt">Grid</span>::<span class="dt">File</span>.new(<span class="ot">@contents</span>.read, description )
id=<span class="dv">self</span>.class.mongo_client.database.fs.insert_one(grid_file)
<span class="ot">@id</span>=id.to_s
<span class="kw">end</span>
<span class="kw">end</span></code></pre></li>
<li><p>Take the new method for a test drive.</p>
<p>Launch the <code>rails console</code></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">4</span>)</code></pre>
<p>Create an (File) IO object with the contents of a file</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> os_file=<span class="dt">File</span>.open(<span class="st">"./db/image1.jpg"</span>)
=> <span class="co">#<File:./db/image1.jpg> </span></code></pre>
<p>New up a model instance, passing in the IO object as the <code>contents</code> and other user-provided fields</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> f=<span class="dt">GridFsFile</span>.new(<span class="st">:author</span> => <span class="st">"kiran"</span>, <span class="st">:topic</span> => <span class="st">"cool place"</span>, <span class="st">:contentType=</span>><span class="st">"image/jpeg"</span>,
<span class="st">:filename=</span>><span class="st">"town1.jpg"</span>, <span class="st">:contents=</span>>os_file)
=> <span class="co">#<GridFsFile:0x00000005a836d0 @id=nil, @author="kiran", @topic="cool place", </span>
<span class="ot">@chunkSize</span>=<span class="dv">nil</span>, <span class="ot">@uploadDate</span>=<span class="dv">nil</span>, <span class="ot">@contentType</span>=<span class="st">"image/jpeg"</span>, <span class="ot">@filename</span>=<span class="st">"town1.jpg"</span>,
<span class="ot">@length</span>=<span class="dv">nil</span>, <span class="ot">@md5</span>=<span class="dv">nil</span>, <span class="ot">@contents</span>=<span class="co">#<File:./db/image1.jpg>> </span></code></pre>
<p>Save the file info and contents to GridFS</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> f.save
=> <span class="st">"56458c18e301d0d09c000004"</span> </code></pre></li>
</ol>
<h3 id="add-a-find-to-return-a-single-model-instance">Add a Find to Return a Single Model Instance</h3>
<ol style="list-style-type: decimal">
<li><p>Declare a set of helper methods (one a class method and the other an instance method) to convert the string form of a BSON::ObjectId back to object form and return that in a query hash since we will be making use of the <code>id</code> mostly in that manner. The instance method operates on the <code>@id</code> attribute. The class method operates on the <code>id</code> passed in as an argument.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> <span class="dv">self</span>.id_criteria id
{_id<span class="st">:BSON</span>::<span class="dt">ObjectId</span>.from_string(id)}
<span class="kw">end</span>
<span class="kw">def</span> id_criteria
<span class="dv">self</span>.class.id_criteria <span class="ot">@id</span>
<span class="kw">end</span></code></pre></li>
<li><p>Declare a class method to use the <code>fs.find</code> method to locate the file info in GridFS. Use the <code>id_criteria</code> helper method we just created to build a query hash expression for the primary key. Note that we are not yet querying for the file object. That will not occur until we need the contents.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> <span class="dv">self</span>.find id
f=mongo_client.database.fs.find(id_criteria(id)).first
<span class="kw">return</span> f.nil? ? <span class="dv">nil</span> : <span class="dt">GridFsFile</span>.new(f)
<span class="kw">end</span></code></pre></li>
<li><p>Take the new method for a test drive.</p>
<p>Reload the new class implementation into <code>rails console</code>.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> reload!</code></pre>
<p>If you do not remember your file ID, use the <code>mongo_client</code> and the <code>find.first</code> command to get a sample file.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> <span class="dt">GridFsFile</span>.mongo_client.database.fs.find.first[<span class="st">:_id</span>].to_s
=> <span class="st">"56458c18e301d0d09c000004"</span></code></pre>
<p>Get the file info from GridFS and wrap in a Model instance.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> f=<span class="dt">GridFsFile</span>.find <span class="st">"56458c18e301d0d09c000004"</span>
=> <span class="co">#<GridFsFile:0x000000059bf870 @id="56458c18e301d0d09c000004", </span>
<span class="ot">@author</span>=<span class="st">"kiran"</span>,
<span class="ot">@topic</span>=<span class="st">"cool place"</span>,
<span class="ot">@chunkSize</span>=<span class="dv">261120</span>,
<span class="ot">@uploadDate</span>=<span class="dv">2015-11-13</span> <span class="bn">07</span>:<span class="bn">07</span>:<span class="bn">04</span> <span class="dt">UTC</span>,
<span class="ot">@contentType</span>=<span class="st">"image/jpeg"</span>,
<span class="ot">@filename</span>=<span class="st">"town1.jpg"</span>,
<span class="ot">@length</span>=<span class="dv">307797</span>,
<span class="ot">@md5</span>=<span class="st">"3468ca1c23cc13ac6af493c4642cc72a"</span>,
<span class="ot">@contents</span>=<span class="dv">nil</span>>
> f.filename
=> <span class="st">"town1.jpg"</span>
> f.length
=> <span class="dv">307797</span> </code></pre></li>
</ol>
<h3 id="get-data-contents-from-gridfs">Get Data Contents from GridFS</h3>
<ol style="list-style-type: decimal">
<li><p>Add an instance method to implement a custom getter for the <code>contents</code> attribute. This method will use <code>fs.find_one</code> to locate the file object matching the criteria generated by the <code>id_criteria</code> helper method and the instance's primary key. The array of chunks is reduced to a single buffer returned to the caller.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> contents
<span class="dt">Rails</span>.logger.debug {<span class="st">"getting gridfs content </span><span class="ot">#{@id}</span><span class="st">"</span>}
f=<span class="dv">self</span>.class.mongo_client.database.fs.find_one(id_criteria)
<span class="kw">if</span> f
buffer = <span class="st">""</span>
f.chunks.reduce([]) <span class="kw">do</span> |x,chunk|
buffer << chunk.data.data
<span class="kw">end</span>
<span class="kw">return</span> buffer
<span class="kw">end</span>
<span class="kw">end</span></code></pre></li>
<li><p>Take the new method for a test drive.</p>
<p>With a handle to the file, we can obtain the bytes of the file data content and simply return the size of the buffer used.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> reload
> <span class="dt">GridFsFile</span>.mongo_client.database.fs.find.first[<span class="st">:_id</span>].to_s
=> <span class="st">"56458c18e301d0d09c000004"</span>
> f=<span class="dt">GridFsFile</span>.find <span class="st">"56458c18e301d0d09c000004"</span>
> f.contents.length
=> <span class="dv">319998</span></code></pre></li>
</ol>
<h3 id="add-a-find-of-all-model-instances">Add a Find of All Model Instances</h3>
<ol style="list-style-type: decimal">
<li><p>Create an instance method to return a collection of model instances that represent the files in GridFS. Note that this is just the file information and not the file data content.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> <span class="kw">def</span> <span class="dv">self</span>.all
files=[]
mongo_client.database.fs.find.each <span class="kw">do</span> |r|
files << <span class="dt">GridFsFile</span>.new(r)
<span class="kw">end</span>
<span class="kw">return</span> files
<span class="kw">end</span></code></pre></li>
<li><p>Take the new method for a test drive.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> reload
> pp <span class="dt">GridFsFile</span>.all.to_a
[<span class="co">#<GridFsFile:0x000000039beda0</span>
<span class="ot">@author</span>=<span class="st">"kiran"</span>,
<span class="ot">@chunkSize</span>=<span class="dv">261120</span>,
<span class="ot">@contentType</span>=<span class="st">"image/jpeg"</span>,
<span class="ot">@contents</span>=<span class="dv">nil</span>,
<span class="ot">@filename</span>=<span class="st">"town1.jpg"</span>,
<span class="ot">@id</span>=<span class="st">"56458c18e301d0d09c000004"</span>,
<span class="ot">@length</span>=<span class="dv">307797</span>,
<span class="ot">@md5</span>=<span class="st">"3468ca1c23cc13ac6af493c4642cc72a"</span>,
<span class="ot">@topic</span>=<span class="st">"cool place"</span>,
<span class="ot">@uploadDate</span>=<span class="dv">2015-11-13</span> <span class="bn">07</span>:<span class="bn">07</span>:<span class="bn">04</span> <span class="dt">UTC</span>>]</code></pre></li>
</ol>
<h3 id="add-an-update-of-the-model-instance">Add an Update of the Model Instance</h3>
<ol style="list-style-type: decimal">
<li><p>Just leave this empty for now. We will not be updating files.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> update params
<span class="co">#TODO</span>
<span class="kw">end</span></code></pre></li>
</ol>
<h3 id="add-a-delete-of-the-model-instance">Add a Delete of the Model Instance</h3>
<ol style="list-style-type: decimal">
<li><p>Add an instance method to destroy the file associated with the instance's primary key. We use the <code>fs.find</code> method and our helper <code>id_criteria</code> to locate and delete the file info and contents from GridFS.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> destroy
<span class="dv">self</span>.class.mongo_client.database.fs.find(id_criteria).delete_one
<span class="kw">end</span></code></pre></li>
<li><p>Take the new method for a test drive.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> reload!
> f=<span class="dt">GridFsFile</span>.find <span class="st">"56458c18e301d0d09c000004"</span>
> f.destroy
=> <span class="co">#<Mongo::Operation::Result:50114800 documents=[{"ok"=>1, "n"=>1}]> </span>
> pp <span class="dt">GridFsFile</span>.all.to_a
=> [] </code></pre></li>
</ol>
<h2 id="add-rails-scaffold">Add Rails Scaffold</h2>
<ol style="list-style-type: decimal">
<li><p>Generate a controller and view that can process all attributes. Note that we are violating the Rails standard by using the camelCase attribute names provided by GridFS here to save some field making (i.e., mapping content_type <-> contentType). It may be worth it to cut down on transation code in a demo like this, but add the mapping in a real application. Note also that we are declaring uploadDate as a string. That is because this field is internally generated by GridFS at upload time and we will treat it as a read-only attribute. The default text display of a date looks much better than the default date widget added by Rails when this is a read-only field.</p>
<pre><code>$ rails g scaffold_controller GridFsFile filename contentType author topic \
uploadDate length:integer chunkSize:integer md5 contents</code></pre></li>
<li><p>Update the <code>routes.rb</code> to add our resource and make it the root URI for the application.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="dt">Rails</span>.application.routes.draw <span class="kw">do</span>
root to: <span class="st">'grid_fs_files#index'</span>
resources <span class="st">:grid_fs_files</span></code></pre>
<pre class="shell"><code>$ rake routes
Prefix Verb URI Pattern Controller#Action
root GET / grid_fs_files#index
grid_fs_files GET /grid_fs_files(.:format) grid_fs_files#index
POST /grid_fs_files(.:format) grid_fs_files#create
new_grid_fs_file GET /grid_fs_files/new(.:format) grid_fs_files#new
edit_grid_fs_file GET /grid_fs_files/:id/edit(.:format) grid_fs_files#edit
grid_fs_file GET /grid_fs_files/:id(.:format) grid_fs_files#show
PATCH /grid_fs_files/:id(.:format) grid_fs_files#update
PUT /grid_fs_files/:id(.:format) grid_fs_files#update
DELETE /grid_fs_files/:id(.:format) grid_fs_files#destroy</code></pre></li>
</ol>
<h2 id="serve-up-file-contents">Serve Up File Contents</h2>
<ol style="list-style-type: decimal">
<li><p>Add an additional route to our controller for data content</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"> get <span class="st">'/grid_fs_files/contents/:id/'</span>, to: <span class="st">'grid_fs_files#contents'</span>, as: <span class="st">'contents'</span></code></pre>
<pre class="shell"><code>$ rake routes
Prefix Verb URI Pattern Controller#Action
contents GET /grid_fs_files/contents/:id(.:format) grid_fs_files#contents</code></pre></li>
<li><p>Implment the <code>contents</code> action in terms of getting the model instance associated with the <code>id</code> and returning the image contents.</p>
<p>Add the contents method to the <code>before_action</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">GridFsFilesController</span> < <span class="dt">ApplicationController</span>
before_action <span class="st">:set_grid_fs_file</span>, only: [<span class="st">:show</span>, <span class="st">:edit</span>, <span class="st">:update</span>, <span class="st">:destroy</span>, <span class="st">:contents</span>]</code></pre>
<p>Add the contents method that sends the data from the model.contents with several HTTP content properties.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">def</span> contents
send_data <span class="ot">@grid_fs_file</span>.contents,
{filename: <span class="ot">@grid_fs_file</span>.filename, type: <span class="ot">@grid_fs_file</span>.contentType, disposition: <span class="st">'inline'</span>}
<span class="kw">end</span></code></pre></li>
<li><p>Start the server and take the new controller method for a test drive.</p>
<p>Start the server</p>
<pre class="shell"><code>$ rails s</code></pre>
<p>Populate GridFS with an image from <code>rails console</code></p>
<pre class="sourceCode ruby"><code class="sourceCode ruby">> os_file=<span class="dt">File</span>.open(<span class="st">"./db/image1.jpg"</span>)
=> <span class="co">#<File:./db/image1.jpg> </span>
> f=<span class="dt">GridFsFile</span>.new(<span class="st">:author</span> => <span class="st">"kiran"</span>, <span class="st">:topic</span> => <span class="st">"cool place"</span>, <span class="st">:contentType=</span>><span class="st">"image/jpeg"</span>,
<span class="st">:filename=</span>><span class="st">"town1.jpg"</span>, <span class="st">:contents=</span>>os_file)
> f.save
=> <span class="st">"5645a2b3e301d0d09c000017"</span> </code></pre>
<p>Access the image from the following URL, replacing the BSON::ObjectId with whatever image you wish to access.</p>
<pre class="shell"><code>http://localhost:3000/grid_fs_files/contents/5645a2b3e301d0d09c000017</code></pre></li>
</ol>
<h2 id="add-upload-and-ui-display">Add Upload and UI Display</h2>
<h3 id="update-view-references-to-content">Update View References to <code>content</code></h3>
<ol style="list-style-type: decimal">
<li><p>Update the fields displayed on the HTML index page (<code>app/views/grid_fs_files/index.html.erb</code>) to include a thumbnail version of the image and remove some of the larger fields (e.g., md5) that are available on the show page.</p>
<p><code>html <th>Contents</th> <th>Filename</th> <th>Contenttype</th> <th>Author</th> <th>Topic</th> <th>Uploaddate</th> <th>Length</th></code> Include a "thumbnail-sized" version of the contents on each line using the <code>img</code> tag.</p>
<p><code>html <td><img height="100px" width="130px" src= <%= contents_path("#{grid_fs_file.id}")%>/></td> <td><%= grid_fs_file.filename %></td> <td><%= grid_fs_file.contentType %></td> <td><%= grid_fs_file.author %></td> <td><%= grid_fs_file.topic %></td> <td><%= grid_fs_file.uploadDate %></td> <td><%= grid_fs_file.length %></td></code></p></li>
<li><p>Remove the <code>contents</code> from the JSON view. <code>app/views/grid_fs_files/index.json.jbuilder</code></p>
<p><code>ruby #TODO: fix this json.extract! grid_fs_file, :id, :filename, :contentType, :author, :topic, :uploadDate, :length, :chunkSize, :md5</code></p></li>
<li><p>Update the <code>app/views/grid_fs_files/_form.html.erb</code> from a <code>text_field</code> to a <code>file_field</code></p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :contents %><span class="kw"><br></span>
<span class="er"><</span>%= f.file_field :contents %>
<span class="kw"></div></span></code></pre></li>
<li><p>Update the show page to display the image from our contents URI with an <code>img</code> tag in <code>app/views/grid_fs_files/show.html.erb</code></p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><p></span>
<span class="kw"><strong></span>Contents:<span class="kw"></strong></span>
<span class="kw"><img</span><span class="ot"> height=</span><span class="st">"1000px"</span><span class="ot"> width=</span><span class="st">"1300px"</span><span class="ot"> src=</span> <span class="er"><</span><span class="st">%=</span><span class="ot"> contents_path</span><span class="er">("#{@grid_fs_file.id}")%</span><span class="kw">></span>/>
<span class="kw"></p></span></code></pre></li>
<li><p>Mark the GridFS-managed fields readonly.</p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :uploadDate %><span class="kw"><br></span>
<span class="er"><</span>%= f.text_field :uploadDate, :readonly => true %>
<span class="kw"></div></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :length %><span class="kw"><br></span>
<span class="er"><</span>%= f.number_field :length, :readonly => true %>
<span class="kw"></div></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :chunkSize %><span class="kw"><br></span>
<span class="er"><</span>%= f.number_field :chunkSize, :readonly => true %>
<span class="kw"></div></span>
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"field"</span><span class="kw">></span>
<span class="er"><</span>%= f.label :md5 %><span class="kw"><br></span>
<span class="er"><</span>%= f.text_field :md5, :readonly => true %>
<span class="kw"></div></span></code></pre></li>
</ol>
<h3 id="take-for-a-test-drive">Take for a Test Drive</h3>
<ol style="list-style-type: decimal">
<li><p>Navigate to root URL</p>
<pre class="url"><code>http://localhost:3000/</code></pre></li>
<li><p>Click <code>New Grid_fs_file</code></p></li>
<li><p>File in the following fields. Do not bother typing in the other fields that are supplied by GridFS.</p>
<ul>
<li>Filename</li>
<li>Contenttype</li>
<li>Author</li>
<li>Topic</li>
</ul></li>
<li><p>Select <code>Choose File</code> and select image</p></li>
<li><p>Click <code>Create Grid_fs_file</code></p></li>
<li><p>Click <code>Back</code> to go back to index.</p></li>
</ol>
<h2 id="heroku-deployment">Heroku Deployment</h2>
<p>This deployment assumes that you have already deployed the <code>Zips</code> and <code>GeoZips</code> applications and will quickly go thru the steps taken to reach Heroku deployment.</p>
<ol style="list-style-type: decimal">
<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 fullstack-course3-module2-gridfsfiles
$ 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>Verify the Gemfile is setup to support ActiveRecord when deployed to Heroku. This is required because we have not removed it from our application.</p>
<pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="co"># Use sqlite3 as the database for Active Record</span>
gem <span class="st">'sqlite3'</span>, group: <span class="st">:development</span>
...
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></li>
<li><p>Create a new MongoDB database and database user on <a href="https://mongolab.com/create"><code>MongoLabs</code></a></p></li>
<li><p>Add a <code>MONGOLAB_URI</code> environment variable to the environment to define the databaase connection when deployed to Heroku. <code>dbhost</code> is both host and port# concatenated together, separated by a ":" (host:port) in this example.</p>
<pre class="shell"><code>$ heroku config:add MONGOLAB_URI=mongodb://dbuser:dbpass@dbhost/dbname</code></pre></li>
<li><p>Verify the <code>config/mongoid.yml</code> has a <code>production</code> profile to accept the newly added environment variable.</p>
<pre class="shell"><code>production:
clients:
default:
uri: <%= ENV['MONGOLAB_URI'] %>
options:
connect_timeout: 15</code></pre></li>
<li><p>Run bundle and commit any changes.</p></li>
<li><p>Deploy application</p>
<pre class="shell"><code>$ git push heroku master</code></pre></li>
</ol>
<h2 id="access-application">Access Application</h2>
<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 "New Grid gs file" to upload a new image into GridFS.</p></li>
</ol>