populate-me 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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
+