populate-me 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +20 -0
  5. data/README.md +655 -0
  6. data/Rakefile +14 -0
  7. data/example/config.ru +100 -0
  8. data/lib/populate_me.rb +2 -0
  9. data/lib/populate_me/admin.rb +157 -0
  10. data/lib/populate_me/admin/__assets__/css/asmselect.css +63 -0
  11. data/lib/populate_me/admin/__assets__/css/jquery-ui.min.css +6 -0
  12. data/lib/populate_me/admin/__assets__/css/main.css +244 -0
  13. data/lib/populate_me/admin/__assets__/img/help/children.png +0 -0
  14. data/lib/populate_me/admin/__assets__/img/help/create.png +0 -0
  15. data/lib/populate_me/admin/__assets__/img/help/delete.png +0 -0
  16. data/lib/populate_me/admin/__assets__/img/help/edit.png +0 -0
  17. data/lib/populate_me/admin/__assets__/img/help/form.png +0 -0
  18. data/lib/populate_me/admin/__assets__/img/help/list.png +0 -0
  19. data/lib/populate_me/admin/__assets__/img/help/login.png +0 -0
  20. data/lib/populate_me/admin/__assets__/img/help/logout.png +0 -0
  21. data/lib/populate_me/admin/__assets__/img/help/menu.png +0 -0
  22. data/lib/populate_me/admin/__assets__/img/help/overview.png +0 -0
  23. data/lib/populate_me/admin/__assets__/img/help/save.png +0 -0
  24. data/lib/populate_me/admin/__assets__/img/help/sort.png +0 -0
  25. data/lib/populate_me/admin/__assets__/img/help/sublist.png +0 -0
  26. data/lib/populate_me/admin/__assets__/js/asmselect.js +412 -0
  27. data/lib/populate_me/admin/__assets__/js/columnav.js +87 -0
  28. data/lib/populate_me/admin/__assets__/js/jquery-ui.min.js +7 -0
  29. data/lib/populate_me/admin/__assets__/js/main.js +388 -0
  30. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  31. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  32. data/lib/populate_me/admin/views/help.erb +94 -0
  33. data/lib/populate_me/admin/views/page.erb +189 -0
  34. data/lib/populate_me/api.rb +124 -0
  35. data/lib/populate_me/attachment.rb +186 -0
  36. data/lib/populate_me/document.rb +192 -0
  37. data/lib/populate_me/document_mixins/admin_adapter.rb +149 -0
  38. data/lib/populate_me/document_mixins/callbacks.rb +125 -0
  39. data/lib/populate_me/document_mixins/outcasting.rb +83 -0
  40. data/lib/populate_me/document_mixins/persistence.rb +95 -0
  41. data/lib/populate_me/document_mixins/schema.rb +198 -0
  42. data/lib/populate_me/document_mixins/typecasting.rb +70 -0
  43. data/lib/populate_me/document_mixins/validation.rb +44 -0
  44. data/lib/populate_me/file_system_attachment.rb +40 -0
  45. data/lib/populate_me/grid_fs_attachment.rb +103 -0
  46. data/lib/populate_me/mongo.rb +160 -0
  47. data/lib/populate_me/s3_attachment.rb +120 -0
  48. data/lib/populate_me/variation.rb +38 -0
  49. data/lib/populate_me/version.rb +4 -0
  50. data/populate-me.gemspec +34 -0
  51. data/test/helper.rb +37 -0
  52. data/test/test_admin.rb +183 -0
  53. data/test/test_api.rb +246 -0
  54. data/test/test_attachment.rb +167 -0
  55. data/test/test_document.rb +128 -0
  56. data/test/test_document_admin_adapter.rb +221 -0
  57. data/test/test_document_callbacks.rb +151 -0
  58. data/test/test_document_outcasting.rb +247 -0
  59. data/test/test_document_persistence.rb +83 -0
  60. data/test/test_document_schema.rb +280 -0
  61. data/test/test_document_typecasting.rb +128 -0
  62. data/test/test_grid_fs_attachment.rb +239 -0
  63. data/test/test_mongo.rb +324 -0
  64. data/test/test_s3_attachment.rb +281 -0
  65. data/test/test_variation.rb +91 -0
  66. data/test/test_version.rb +11 -0
  67. metadata +294 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b2770ba89bcfa96ad56baafdf141c67891317044051f44b117a8e6b438f7c432
4
+ data.tar.gz: f076278f23d91e5c59c141b39698c34a9083c5861ba01f41a3692d587bc0b73b
5
+ SHA512:
6
+ metadata.gz: f952a4160015147d54bab9a504f189613521e33342561e95ae33710cb2c3929a9a9512a96d6b0ed2812571a3faa7c972f3b550556884e44c9c0cd7df19922ed0
7
+ data.tar.gz: cd10b6ec3d88432f6255803150c6c1b129b0ffab6a6736d92243238976cfbf295413ea5a1cd9251029edfd798cdead3ac98fcbf51cd3f415a0011702ada41c5b
@@ -0,0 +1,9 @@
1
+ .DS_STORE
2
+ *.swp
3
+ *.sass-cache
4
+ .ruby-version
5
+ pkg/
6
+ *.gem
7
+ Gemfile.lock
8
+ .bundle/
9
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Mickael Riga
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
@@ -0,0 +1,655 @@
1
+ Populate Me
2
+ ===========
3
+
4
+ Overview
5
+ --------
6
+
7
+ `PopulateMe` is a modular system which provides an admin backend for any
8
+ Ruby/Rack web application. It is made with Sinatra but you can code your
9
+ frontend with any other Framework like Rails.
10
+
11
+ Table of contents
12
+ ----------------
13
+
14
+ - [Overview](#overview)
15
+ - [Table of contents](#table-of-contents)
16
+ - [Documents](#documents)
17
+ - [Schema](#schema)
18
+ - [Relationships](#relationships)
19
+ - [Validations](#validations)
20
+ - [Callbacks](#callbacks)
21
+ - [Single Documents](#single-documents)
22
+ - [Mongo documents](#mongo-documents)
23
+ - [Admin](#admin)
24
+ - [Polymorphism](#polymorphism)
25
+ - [Customize Admin](#customize-admin)
26
+ - [API](#api)
27
+
28
+ Documents
29
+ ---------
30
+
31
+ The `Document` class is a prototype. It contains all the code that is
32
+ not specific to a database system. When using this class, documents are
33
+ just kept in memory and therefore are lost when you restart the app.
34
+
35
+ Obviously, in a real application, you would not use this class, but
36
+ a persistent one instead. But since the purpose is to have a common
37
+ interface for any database system, then the following examples are
38
+ written using the basic `Document` class.
39
+
40
+ For the moment, `PopulateMe` only ships with a [MongoDB](#mongo-documents) document, but we hope there will be others in the future, including some for SQL databases.
41
+
42
+ ### Schema
43
+
44
+ Here is an example of a document class:
45
+
46
+ ```ruby
47
+ require 'populate_me/document'
48
+
49
+ class BlogArticle < PopulateMe::Document
50
+
51
+ field :title, default: 'New blog article', required: true
52
+ field :content, type: :text
53
+ field :created_on, type: :datetime, default: proc{Time.now}
54
+ field :published, type: :boolean
55
+
56
+ sort_by :created_on, :desc
57
+
58
+ end
59
+ ```
60
+
61
+ Quite common so far.
62
+ The `field` method allows you to record anything about the field
63
+ itself, but here are the keys used by `PopulateMe`:
64
+
65
+ - `:type` Defines the type of field (please find the list of types below).
66
+ - `:form_field` Set to `false` if you do not want this field in the default form.
67
+ - `:label` What the label in the form says (defaults to a human-friendly version of the field name)
68
+ - `:wrap` Set it to false if you do not want the form field to be wrapped in a `div` with a label.
69
+ - `:default` Either a default value or a `proc` to run to get the default value.
70
+ - `:required` Set to true if you want the field to be marked as required in the form.
71
+ - `:only_for` List of polymorphic type values
72
+
73
+ As you can see, most of the options are made for you to tailor the form
74
+ which `PopulateMe` will generate for you in the admin.
75
+
76
+ Available types are:
77
+
78
+ - `:string` Short text.
79
+ - `:text` Multiline text.
80
+ - `:boolean` Which is `true` or `false`.
81
+ - `:select` Dropdown list of options (records a string).
82
+
83
+ A `:list` type exists as well for nested documents, but it is not
84
+ fully working yet.
85
+
86
+ The `field` method creates a getter and a setter for this particular field.
87
+
88
+ ```ruby
89
+ blog_article.published # Returns true or false
90
+ blog_article.published = true
91
+ ```
92
+
93
+ ### Relationships
94
+
95
+ In its simplest form, when using the modules convention, relationships
96
+ can be declared this way:
97
+
98
+ ```ruby
99
+ class BlogArticle < PopulateMe::Document
100
+
101
+ field :title
102
+
103
+ relationship :comments
104
+
105
+ end
106
+
107
+ class BlogArticle::Comment < PopulateMe::Document
108
+
109
+ field :author
110
+ field :blog_article_id, type: :hidden
111
+ position_field scope: :blog_article_id
112
+
113
+ end
114
+ ```
115
+
116
+ The `relationship` method creates 2 getters for this particular field,
117
+ one with the same name and one with `_first` at the end. Both are cached
118
+ so that the database is queried only once.
119
+
120
+ ```ruby
121
+ blog_article.comments # Returns all the comments for this article
122
+ blog_article.comments_first # Returns the first comment for this article
123
+ ```
124
+
125
+ It uses the `PopulateMe::Document::admin_find` and
126
+ `PopulateMe::Document::admin_find_first` methods in the background,
127
+ so default sorting order is respected.
128
+
129
+ ### Validations
130
+
131
+ In its simplest form, validations are done by overriding the `#validate` method and declaring errors with the `#error_on` method.
132
+
133
+ ```ruby
134
+ class Person < PopulateMe::Document
135
+
136
+ field :name
137
+
138
+ def validate
139
+ error_on(:name, 'Cannot be fake') if self.name=='John Doe'
140
+ end
141
+
142
+ end
143
+ ```
144
+
145
+ If you don't use the `PopulateMe` interface and create a document
146
+ programmatically, here is what it could look like:
147
+
148
+ ```ruby
149
+ person = Person.new(name: 'John Doe')
150
+ person.new? # returns true
151
+ person.save # fails
152
+ person.valid? # returns false
153
+ person.errors # returns { name: ['Cannot be fake'] }
154
+ ```
155
+
156
+ ### Callbacks
157
+
158
+ There are some classic hooks which trigger the callbacks you declare.
159
+ Here is a basic example:
160
+
161
+ ```ruby
162
+ require 'populate_me/document'
163
+
164
+ class Person < PopulateMe::Document
165
+
166
+ field :firstname
167
+ field :lastname
168
+ field :fullname, form_field: false
169
+
170
+ before :save do
171
+ self.fullname = "#{self.firstname} #{self.lastname}"
172
+ end
173
+
174
+ after :delete, :goodbye
175
+
176
+ def goodbye
177
+ puts "So long and thanks for all the fish"
178
+ end
179
+
180
+ end
181
+ ```
182
+
183
+ First you can note that the field option `form_field: false` makes it a field
184
+ that does not appear in the form. This is generally the case for fields that
185
+ are generated from other fields.
186
+
187
+ Anyway, here we define a callback which `PopulateMe` runs each time a document
188
+ is saved. And with the second one, you can see that we can pass the name of
189
+ a method instead of a block.
190
+
191
+ The list of hooks is quite common but here it is as a reminder:
192
+
193
+ - `before :validate`
194
+ - `after :validate`
195
+ - `before :create`
196
+ - `after :create`
197
+ - `before :update`
198
+ - `after :update`
199
+ - `before :save` (both create or update)
200
+ - `after :save` (both create or update)
201
+ - `before :delete`
202
+ - `after :delete`
203
+
204
+ Now you can register many callbacks for the same hook. They will be chained in
205
+ the order you register them. However, if for any reason you need to register a
206
+ callback and make sure it runs before the others, you can add `prepend: true`.
207
+
208
+ ```ruby
209
+ before :save, prepend: true do
210
+ puts 'Shotgun !!!'
211
+ end
212
+ ```
213
+
214
+ If you want to go even further and create your own hooks, this is very easy.
215
+ You can create a hook like this:
216
+
217
+ ```ruby
218
+ document.exec_callback(:my_hook)
219
+ ```
220
+
221
+ And you would then register a callback like this:
222
+
223
+ ```ruby
224
+ register_callback :my_hook do
225
+ # Do something...
226
+ end
227
+ ```
228
+
229
+ You can use `before` and `after` as well. In fact this:
230
+
231
+ ```ruby
232
+ after :lunch do
233
+ # Do something...
234
+ end
235
+ ```
236
+
237
+ Is equivalent to:
238
+
239
+ ```ruby
240
+ register_callback :after_lunch do
241
+ # Do something...
242
+ end
243
+ ```
244
+
245
+ ### Single Documents
246
+
247
+ Sometimes you want a collection with only one document, like for recording
248
+ settings for example. In this case you can use the `::is_unique` class method.
249
+
250
+ ```ruby
251
+ require 'populate_me/document'
252
+
253
+ class GeneralWebsiteSettings < PopulateMe::Document
254
+ field :main_meta_title
255
+ field :main_meta_description
256
+ field :google_analytics_ref
257
+ end
258
+
259
+ GeneralWebsiteSettings.is_unique
260
+ ```
261
+
262
+ It just creates the document if it does not exist yet with the ID `unique`.
263
+ If you want a different ID, you can pass it as an argument.
264
+
265
+ Just make sure that if you have fields with `required: true`, they also have
266
+ a `:default` value. Otherwise the creation of the document will fail because it
267
+ is not `self.valid?`.
268
+
269
+ ### Mongo Documents
270
+
271
+ Note: the current version works with the mongo driver version 2
272
+
273
+ Now let's declare a real document class which can persist on a database,
274
+ the `MongoDB` kind of document. The first thing we need to clarify is the
275
+ setup. Here is a classic setup:
276
+
277
+ ```ruby
278
+ # lib/db.rb
279
+ require 'mongo'
280
+ require 'populate_me/mongo'
281
+
282
+ client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'your-database-name')
283
+
284
+ PopulateMe::Mongo.set :db, client.database
285
+
286
+ require 'person'
287
+ ```
288
+
289
+ Then the document is pretty much the same as the prototype except that it
290
+ subclasses `PopulateMe::Mongo` instead.
291
+
292
+ ```ruby
293
+ # lib/person.rb
294
+ require 'populate_me/mongo'
295
+
296
+ class Person < PopulateMe::Mongo
297
+ field :firstname
298
+ field :lastname
299
+ end
300
+ ```
301
+
302
+ As you can see in setup, you can define inheritable settings on
303
+ `PopulateMe::Mongo`, meaning that any subclass after this will have the `:db`
304
+ and you can set it only once.
305
+
306
+ Nevertheless it is obviously possible to set a different `:db` for each class.
307
+
308
+ ```ruby
309
+ # lib/person.rb
310
+ require 'populate_me/mongo'
311
+
312
+ class Person < PopulateMe::Mongo
313
+
314
+ set :db, $my_db
315
+
316
+ field :firstname
317
+ field :lastname
318
+
319
+ end
320
+ ```
321
+
322
+ This is particularly useful if you keep a type of documents in a different
323
+ location for example. Otherwise it is more convenient to set it once
324
+ and for all.
325
+
326
+ You can also set `:collection_name`, but in most cases you would let `PopulateMe`
327
+ defaults it to the dasherized class name. So `BlogArticle::Comment` would be
328
+ in the collection called `blog-article--comment`.
329
+
330
+ Whatever you choose, you will have access to the collection object with the
331
+ `::collection` class method. Which allows you to do anything the driver does.
332
+
333
+ ```ruby
334
+ first_pedro = Person.collection.find({ 'firstname' => 'Pedro' }).first
335
+ mcs = Person.collection.find({ 'lastname' => /^Mc/i })
336
+ ```
337
+
338
+ Although since these are methods from the driver, `first_pedro` returns a hash,
339
+ and `mcs` returns a `Mongo::Collection::View`. If you want document object, you can use
340
+ the `::cast` class method which takes a block in the class context/scope and
341
+ casts either a single hash into a full featured document, or casts the items of
342
+ an array (or anything which responds to `:map`).
343
+
344
+ ```ruby
345
+ first_pedro = Person.cast{ collection.find_one({ 'firstname' => 'Pedro' }) }
346
+ mcs = Person.cast{ collection.find({ 'lastname' => /^Mc/i }) }
347
+ first_pedro.class # returns Person
348
+ mcs[0].class # returns Person
349
+ ```
350
+
351
+ Admin
352
+ -----
353
+
354
+ A basic admin would look like this:
355
+
356
+ ```ruby
357
+ # lib/admin.rb
358
+ require "populate_me/admin"
359
+
360
+ class Admin < PopulateMe::Admin
361
+ # Since we are in lib we use this to move
362
+ # the root one level up.
363
+ # Not mandatory but useful if you plan to have
364
+ # custom views in the main views folder
365
+ set :root, ::File.expand_path('../..', __FILE__)
366
+ # Only if you use Rack::Cerberus for authentication
367
+ # you can pass the settings
368
+ set :cerberus, {company_name: 'Nintendo'}
369
+ # Build menu and sub-menus
370
+ set :menu, [
371
+ ['Settings', '/admin/form/settings/unique'],
372
+ ['Articles', '/admin/list/article'],
373
+ ['Staff', [
374
+ ['Designers', '/admin/list/staff-member?filter[job]=Designer'],
375
+ ['Developers', '/admin/list/staff-member?filter[job]=Developer'],
376
+ ]]
377
+ ]
378
+ end
379
+ ```
380
+
381
+ So the main thing you need is to define your menu. Then mount it in
382
+ your `config.ru` whereever you want.
383
+
384
+ ```ruby
385
+ # config.ru
386
+ require 'admin'
387
+
388
+ map '/admin' do
389
+ run Admin
390
+ end
391
+ ```
392
+
393
+ Most of the URLs in your menu will probably be for the admin itself and use
394
+ the admin URL patterns, but this is not mandatory. A link to an external page
395
+ would load in a new tab. Whereas admin URLs create columns in the `PopulateMe`
396
+ user interface. Many things are possible with these patterns but here are
397
+ the main ones:
398
+
399
+ - `/:path_to_admin/list/:dasherized_document_class` This gives you the list of
400
+ documents from the desired class. They are ordered as specified by `sort_by`.
401
+ You can also filter like in the example to get only specific documents.
402
+ - `:path_to_admin/form/:dasherized_document_class/:id` You would rarely use this
403
+ one which directly opens the form of a specific document, since all this is
404
+ generally accessed from the list page. It doesn't need to be coded. The only is
405
+ probably for [single documents](#single-documents) because they are not part of
406
+ a list. The ID would then be litterally `unique`, or whatever ID you declared
407
+ instead.
408
+
409
+ ### Polymorphism
410
+
411
+ You can use the schema to set a Document class as polymorphic. The consequence
412
+ is that the admin will make you choose a type before creating a new document.
413
+ And then the form will only display the fields applicable to this polymorphic
414
+ type. And once created, it will only show relationships applicable to its
415
+ polymorphic type. You can do this with the `:only_for` option.
416
+
417
+ Here is an example of a document that can either be a title with a paragraph, or
418
+ a title with a set of images:
419
+
420
+ ```ruby
421
+ # lib/models/box.rb
422
+
423
+ require 'populate_me/document'
424
+
425
+ class Box < PopulateMe::Document
426
+
427
+ field :title
428
+ field :paragraph, type: :text, only_for: 'Paragraph'
429
+ relationship :images, only_for: 'Image slider'
430
+ position_field
431
+
432
+ end
433
+ ```
434
+
435
+ In this case, when you create a `Box` with the polymorphic type `Paragraph`, the
436
+ form will have a field for `:paragraph` but no relationship for images. And if
437
+ you create a `Box` with the polymorphic type `Image slider`, it will be the
438
+ opposite.
439
+
440
+ The option `:only_for` can also be an `Array`. Actually, when inspecting the
441
+ `fields`, you'll see that even when you pass a `String`, it will be put inside
442
+ an `Array`.
443
+
444
+ ```ruby
445
+ Box.fields[:paragraph][:only_for] # => ['Paragraph']
446
+ ```
447
+
448
+ A hidden field is automatically created called `:polymorphic_type`, therefore
449
+ it is a method you can call to get or set the `:polymorphic_type`.
450
+
451
+ ```ruby
452
+ box = Box.new polymorphic_type: 'Paragraph'
453
+ box.polymorphic_type # => 'Paragraph'
454
+ ```
455
+
456
+ One of the information that the field contains is all the `:values` the field
457
+ can have.
458
+
459
+ ```ruby
460
+ Box.fields[:polymorphic_type][:values] # => ['Paragraph', 'Image slider']
461
+ ```
462
+
463
+ They are in the order they are declared in the fields. If you want to just set
464
+ this list yourself or any other option attached to the `:polimorphic_type` field
465
+ you can do so with the `Document::polymorphic` class method.
466
+
467
+ ```ruby
468
+ # lib/models/box.rb
469
+
470
+ require 'populate_me/document'
471
+
472
+ class Box < PopulateMe::Document
473
+
474
+ polymorphic values: ['Image slider', 'Paragraph']
475
+ field :title
476
+ field :paragraph, type: :text, only_for: 'Paragraph'
477
+ relationship :images, only_for: 'Image slider'
478
+ position_field
479
+
480
+ end
481
+ ```
482
+
483
+ If each polymorphic type has a lot of fields and/or relationships, you can use the
484
+ `Document::only_for` class method which sets the `:only_for` option for
485
+ everything inside the block.
486
+
487
+ ```ruby
488
+ # lib/models/media.rb
489
+
490
+ require 'populate_me/document'
491
+
492
+ class Media < PopulateMe::Document
493
+
494
+ field :title
495
+ only_for 'Book' do
496
+ field :author
497
+ field :publisher
498
+ relationship :chapters
499
+ end
500
+ only_for 'Movie' do
501
+ field :script_writer
502
+ field :director
503
+ relationship :scenes
504
+ relationship :actors
505
+ end
506
+ position_field
507
+
508
+ end
509
+ ```
510
+
511
+ It is worth noting that this implementation of polymorphism is supposed to work
512
+ with fixed schema databases, and therefore all fields and relationship exist for
513
+ each document. In our case, books would still have a `#director` method. The
514
+ difference is only cosmetic and mainly allows you to have forms that are less
515
+ crowded in the admin.
516
+
517
+ To mitigate this, a few methods are there to help you. There is a predicate for
518
+ knowing if a class is polymorphic.
519
+
520
+ ```ruby
521
+ Media.polymorphic? # => true
522
+ ```
523
+
524
+ For each document, you can inspect its polymorphic type or check if a field or
525
+ relationship is applicable.
526
+
527
+ ```ruby
528
+ book = Media.new polymorphic_type: 'Book', title: 'Hot Water Music', author: 'Charles Bukowski'
529
+ book.polymorphic_type # => 'Book'
530
+ book.field_applicable? :author # => true
531
+ book.relationship_applicable? :actors # => false
532
+ ```
533
+
534
+ ### Customize Admin
535
+
536
+ You can customize the admin with a few settings.
537
+ The main ones are for adding CSS and javascript.
538
+ There are 2 settings for this: `:custom_css_url` and `:custom_js_url`.
539
+
540
+ ```ruby
541
+ # lib/admin.rb
542
+ require "populate_me/admin"
543
+
544
+ class Admin < PopulateMe::Admin
545
+
546
+ set :custom_css_url, '/css/admin.css'
547
+ set :custom_js_url, '/js/admin.js'
548
+
549
+ set :root, ::File.expand_path('../..', __FILE__)
550
+
551
+ set :menu, [
552
+ ['Settings', '/admin/form/settings/unique'],
553
+ ['Articles', '/admin/list/article'],
554
+ ['Staff', [
555
+ ['Designers', '/admin/list/staff-member?filter[job]=Designer'],
556
+ ['Developers', '/admin/list/staff-member?filter[job]=Developer'],
557
+ ]]
558
+ ]
559
+
560
+ end
561
+ ```
562
+
563
+ Inside the javascript file, you can use many functions and variables that
564
+ are under the `PopulateMe` namespace. See source code to know more about it.
565
+ Some are callbacks like `PopulateMe.custom_init_column` which allows you to
566
+ bind events when a column was created.
567
+
568
+ ```javascript
569
+ # /js/admin.js
570
+
571
+ $(function() {
572
+
573
+ $('body').bind('change', 'select.special', function(event) {
574
+ alert('Changed!');
575
+ });
576
+
577
+ PopulateMe.custom_init_column = function(column) {
578
+ $('select.special', column).css({color: 'orange'});
579
+ }
580
+
581
+ });
582
+ ```
583
+
584
+ The other thing you might want to do is adding mustache templates.
585
+ You can do this with the setting `:custom_templates_view`.
586
+
587
+ ```ruby
588
+ # lib/admin.rb
589
+ require "populate_me/admin"
590
+
591
+ class Admin < PopulateMe::Admin
592
+
593
+ set :custom_templates_view, :custom_templates
594
+
595
+ # ...
596
+
597
+ end
598
+ ```
599
+
600
+ Let's say we want to be able to set the size of the preview for attachments,
601
+ as opposed to the default value of 150. We would put this in the view:
602
+
603
+ ```eruby
604
+ <script id="template-attachment-field-custom" type="x-tmpl-mustache">
605
+ {{#url}}
606
+ <img src='{{url}}{{cache_buster}}' alt='Preview' width='{{attachment_preview_width}}' />
607
+ <button class='attachment-deleter'>x</button>
608
+ <br />
609
+ {{/url}}
610
+ <input type='file' name='{{input_name}}' {{#max_size}}data-max-size='{{max_size}}'{{/max_size}} {{{build_input_atrributes}}} />
611
+ </script>
612
+ ```
613
+
614
+ This is the default template except we've replace `150` with the mustache
615
+ variable `attachment_preview_width`. Everything that you set on the schema
616
+ is available in the template, so you can set both the custom template name
617
+ and the width variable in the hash passed to `field` when doing your schema.
618
+ The template name is the ID of the script tag.
619
+
620
+ ```ruby
621
+ # /lib/blog_post.rb
622
+ require 'populate_me/document'
623
+
624
+ class BlogPost < PopulateMe::Document
625
+
626
+ field :title
627
+ field :image, type: :attachment, custom_template: 'template-attachment-field-custom', attachment_preview_width: 200, variations: [
628
+ PopulateMe::Variation.new_image_magick_job(:thumb, :gif, "-resize '300x'")
629
+ ]
630
+
631
+ # ...
632
+
633
+ end
634
+ ```
635
+
636
+
637
+ API
638
+ ---
639
+
640
+ In a normal use, you most likely don't have anything to do with the `API` module.
641
+ It is just another middleware automatically mounted under `/api` on your `Admin`.
642
+ So if your `Admin` path is `/admin`, then your `API` path is `/admin/api`.
643
+
644
+ The purpose of the `API` module is to provide all the path patterns for creating,
645
+ deleting and updating documents. The interface does all the job for you. But if
646
+ you end up building your all custom interface, you probably want to [have a look
647
+ at the implementation](lib/populate_me/api.rb).
648
+
649
+ Another aspect of the `API` is that it relies on document methods. So if you
650
+ want to create a subclass of `Document`, make sure that you override everything
651
+ that the `API` or the `Admin` may need.
652
+
653
+ This module is derived from a Gem I did called [rack-backend-api](https://github.com/mig-hub/backend-api). It is not maintained any more since `PopulateMe` is the evolution
654
+ of this Gem.
655
+