populate-me 0.0.33 → 0.1.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 (86) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/LICENSE +1 -1
  4. data/README.md +399 -4
  5. data/Rakefile +14 -0
  6. data/example/config.ru +67 -0
  7. data/lib/populate_me.rb +2 -0
  8. data/lib/populate_me/admin.rb +143 -0
  9. data/lib/populate_me/admin/__assets__/css/main.css +174 -0
  10. data/lib/populate_me/admin/__assets__/js/columnav.js +82 -0
  11. data/lib/populate_me/admin/__assets__/js/main.js +251 -0
  12. data/lib/populate_me/admin/__assets__/js/mustache.js +578 -0
  13. data/lib/populate_me/admin/__assets__/js/sortable.js +2 -0
  14. data/lib/populate_me/admin/views/page.erb +163 -0
  15. data/lib/populate_me/api.rb +124 -0
  16. data/lib/populate_me/attachment.rb +182 -0
  17. data/lib/populate_me/document.rb +178 -0
  18. data/lib/populate_me/document_mixins/admin_adapter.rb +131 -0
  19. data/lib/populate_me/document_mixins/callbacks.rb +104 -0
  20. data/lib/populate_me/document_mixins/outcasting.rb +49 -0
  21. data/lib/populate_me/document_mixins/persistence.rb +92 -0
  22. data/lib/populate_me/document_mixins/schema.rb +99 -0
  23. data/lib/populate_me/document_mixins/typecasting.rb +60 -0
  24. data/lib/populate_me/document_mixins/validation.rb +44 -0
  25. data/lib/populate_me/file_system_attachment.rb +40 -0
  26. data/lib/populate_me/grid_fs_attachment.rb +127 -0
  27. data/lib/populate_me/mongo.rb +98 -3
  28. data/lib/populate_me/variation.rb +34 -0
  29. data/lib/populate_me/version.rb +4 -0
  30. data/populate-me.gemspec +20 -12
  31. data/test/helper.rb +37 -0
  32. data/test/test_admin.rb +161 -0
  33. data/test/test_api.rb +246 -0
  34. data/test/test_attachment.rb +155 -0
  35. data/test/test_document.rb +120 -0
  36. data/test/test_document_admin_adapter.rb +43 -0
  37. data/test/test_document_callbacks.rb +107 -0
  38. data/test/test_document_persistence.rb +56 -0
  39. data/test/test_document_typecasting.rb +121 -0
  40. data/test/test_mongo.rb +217 -0
  41. data/test/test_variation.rb +91 -0
  42. data/test/test_version.rb +11 -0
  43. metadata +115 -66
  44. data/lib/populate_me/control.rb +0 -196
  45. data/lib/populate_me/control/_public/css/main.css +0 -207
  46. data/lib/populate_me/control/_public/css/plugin.asmselect.css +0 -63
  47. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_30_cccccc_40x100.png +0 -0
  48. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_flat_50_5c5c5c_40x100.png +0 -0
  49. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_20_555555_1x400.png +0 -0
  50. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_0078a3_1x400.png +0 -0
  51. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_glass_40_ffc73d_1x400.png +0 -0
  52. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_gloss-wave_25_333333_500x100.png +0 -0
  53. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_highlight-soft_80_eeeeee_1x100.png +0 -0
  54. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_25_000000_1x100.png +0 -0
  55. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-bg_inset-soft_30_f58400_1x100.png +0 -0
  56. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_222222_256x240.png +0 -0
  57. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_4b8e0b_256x240.png +0 -0
  58. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_a83300_256x240.png +0 -0
  59. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_cccccc_256x240.png +0 -0
  60. data/lib/populate_me/control/_public/css/ui-darkness/images/ui-icons_ffffff_256x240.png +0 -0
  61. data/lib/populate_me/control/_public/css/ui-darkness/jquery-ui-1.8.17.custom.css +0 -430
  62. data/lib/populate_me/control/_public/img/grip.png +0 -0
  63. data/lib/populate_me/control/_public/img/icons-cms-solarized.png +0 -0
  64. data/lib/populate_me/control/_public/img/icons-cms.png +0 -0
  65. data/lib/populate_me/control/_public/img/placeholder.png +0 -0
  66. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.gif +0 -0
  67. data/lib/populate_me/control/_public/img/placeholder.stash_thumb.png +0 -0
  68. data/lib/populate_me/control/_public/img/small-loader.gif +0 -0
  69. data/lib/populate_me/control/_public/js/addon.timepicker.js +0 -20
  70. data/lib/populate_me/control/_public/js/jquery-ui.js +0 -114
  71. data/lib/populate_me/control/_public/js/jquery.js +0 -167
  72. data/lib/populate_me/control/_public/js/jquery.mustache.js +0 -559
  73. data/lib/populate_me/control/_public/js/main.js +0 -144
  74. data/lib/populate_me/control/_public/js/plugin.asmselect.js +0 -407
  75. data/lib/populate_me/control/_public/js/plugin.form.js +0 -11
  76. data/lib/populate_me/control/_public/js/plugin.quicksearch.js +0 -1
  77. data/lib/populate_me/control/_public/js/plugin.underwood.js +0 -4
  78. data/lib/populate_me/control/views/populate_me_layout.erb +0 -75
  79. data/lib/populate_me/ext.rb +0 -16
  80. data/lib/populate_me/mongo/backend_api_plug.rb +0 -78
  81. data/lib/populate_me/mongo/crushyform.rb +0 -243
  82. data/lib/populate_me/mongo/mutation.rb +0 -324
  83. data/lib/populate_me/mongo/plug.rb +0 -134
  84. data/lib/populate_me/mongo/stash.rb +0 -171
  85. data/test/spec_ext.rb +0 -29
  86. data/test/spec_mongo_mutation.rb +0 -279
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 8164c457284a0daa762745f768ba56acead4592eab6fca62547dfff4d56ad7da
4
- data.tar.gz: f8fa01c9478a8be7fe04c4628f0c3785d4f089203c6eb8961565defbae6e0b66
2
+ SHA1:
3
+ metadata.gz: 5046f2abc8d311d2dedd3c31fac2ac90f6ae0f36
4
+ data.tar.gz: 2df84aed8c576a8a2908977fde658f2ad85ae2e7
5
5
  SHA512:
6
- metadata.gz: 66160cda7918beb24097e28de0189a381c7c72eaea16c184ca8a2e0e2dbb848df73a7af41feb9f195e5942090e483622961386000597a7a625becea4a3208e9b
7
- data.tar.gz: cb4972cd6d06fc192ece77899110fc33533e5f2b02116562d55b60b6f7a6c62e8c4f854e654ebd047828a9ecf9cb0104278aab7cae252f8030d7d5994d1bef54
6
+ metadata.gz: 0f4d57c7dfc72de5c4dc08e027c498e7cee4bd4de3fa2b3cc5facb74c28aaa006e8270d5f79baa1fc0af2b48aec0b297bb0dd9ecc211a28018d37b1ca415c109
7
+ data.tar.gz: 71dd5a2221caa8d1e9d3761c2651e2542f1182ea30f00d50f4b0bb9d5e010d1d8f46df5695b941544aac4af5f83a306cd27243ba03e28a6a2cb4018619446081
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  .DS_STORE
2
2
  *.swp
3
+ *.sass-cache
3
4
  .ruby-version
4
5
  pkg/
5
6
  *.gem
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 Mickael Riga
1
+ Copyright (c) 2016 Mickael Riga
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,9 +1,404 @@
1
1
  Populate Me
2
2
  ===========
3
3
 
4
- !!! WARNING : This project is under construction and therefore is not usable yet.
4
+ Overview
5
+ --------
5
6
 
6
- Nevertheless Populate Me will be a relatively complete but simple CMS.
7
- It will include a Rack middleware for putting in your Rack stack, and a bespoke MongoDB ODM.
8
- Stay tuned.
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
+ - [Validations](#validations)
19
+ - [Relationships](#relationships)
20
+ - [Callbacks](#callbacks)
21
+ - [Single Documents](#single-documents)
22
+ - [Mongo documents](#mongo-documents)
23
+ - [Admin](#admin)
24
+ - [API](#api)
25
+
26
+ Documents
27
+ ---------
28
+
29
+ The `Document` class is a prototype. It contains all the code that is
30
+ not specific to a database system. When using this class, documents are
31
+ just kept in memory and therefore are lost when you restart the app.
32
+
33
+ Obviously, in a real application, you would not use this class, but
34
+ a persistent one instead. But since the purpose is to have a common
35
+ interface for any database system, then the following examples are
36
+ written using the basic `Document` class.
37
+
38
+ 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.
39
+
40
+ ### Schema
41
+
42
+ Here is an example of a document class:
43
+
44
+ ```ruby
45
+ require 'populate_me/document'
46
+
47
+ class BlogArticle < PopulateMe::Document
48
+
49
+ field :title, default: 'New blog article', required: true
50
+ field :content, type: :text
51
+ field :created_on, type: :datetime, default: proc{Time.now}
52
+ field :published, type: :boolean
53
+
54
+ sort_by :created_on, :desc
55
+
56
+ end
57
+ ```
58
+
59
+ Quite common so far.
60
+ The `field` method allows you to record anything about the field
61
+ itself, but here are the keys used by `PopulateMe`:
62
+
63
+ - `:type` Defines the type of field (please find the list of types below).
64
+ - `:form_field` Set to `false` if you do not want this field in the default form.
65
+ - `:label` What the label in the form says (defaults to a human-friendly version of the field name)
66
+ - `:wrap` Set it to false if you do not want the form field to be wrapped in a `div` with a label.
67
+ - `:default` Either a default value or a `proc` to run to get the default value.
68
+ - `:required` Set to true if you want the field to be marked as required in the form.
69
+
70
+ As you can see, most of the options are made for you to tailor the form
71
+ which `PopulateMe` will generate for you in the admin.
72
+
73
+ Available types are:
74
+
75
+ - `:string` Short text.
76
+ - `:text` Multiline text.
77
+ - `:boolean` Which is `true` or `false`.
78
+ - `:select` Dropdown list of options (records a string).
79
+
80
+ A `:list` type exists as well for nested documents, but it is not
81
+ fully working yet.
82
+
83
+ ### Validations
84
+
85
+ In its simplest form, validations are done by overriding the `#validate` method and declaring errors with the `#error_on` method.
86
+
87
+ ```ruby
88
+ class Person < PopulateMe::Document
89
+
90
+ field :name
91
+
92
+ def validate
93
+ error_on(:name, 'Cannot be fake') if self.name=='John Doe'
94
+ end
95
+
96
+ end
97
+ ```
98
+
99
+ If you don't use the `PopulateMe` interface and create a document
100
+ programmatically, here is what it could look like:
101
+
102
+ ```ruby
103
+ person = Person.new(name: 'John Doe')
104
+ person.new? # returns true
105
+ person.save # fails
106
+ person.valid? # returns false
107
+ person.errors # returns { name: ['Cannot be fake'] }
108
+ ```
109
+
110
+ ### Relationships
111
+
112
+ In its simplest form, when using the modules convention, relationships
113
+ can be declared this way:
114
+
115
+ ```ruby
116
+ class BlogArticle < PopulateMe::Document
117
+
118
+ field :title
119
+
120
+ relationship :comments
121
+
122
+ end
123
+
124
+ class BlogArticle::Comment < PopulateMe::Document
125
+
126
+ field :author
127
+ field :blog_article_id, type: :hidden
128
+ position_field scope: :blog_article_id
129
+
130
+ end
131
+ ```
132
+
133
+ ### Callbacks
134
+
135
+ There are some classic hooks which trigger the callbacks you declare.
136
+ Here is a basic example:
137
+
138
+ ```ruby
139
+ require 'populate_me/document'
140
+
141
+ class Person < PopulateMe::Document
142
+
143
+ field :firstname
144
+ field :lastname
145
+ field :fullname, form_field: false
146
+
147
+ before :save do
148
+ self.fullname = "#{self.firstname} #{self.lastname}"
149
+ end
150
+
151
+ after :delete, :goodbye
152
+
153
+ def goodbye
154
+ puts "So long and thanks for all the fish"
155
+ end
156
+
157
+ end
158
+ ```
159
+
160
+ First you can note that the field option `form_field: false` makes it a field
161
+ that does not appear in the form. This is generally the case for fields that
162
+ are generated from other fields.
163
+
164
+ Anyway, here we define a callback which `PopulateMe` runs each time a document
165
+ is saved. And with the second one, you can see that we can pass the name of
166
+ a method instead of a block.
167
+
168
+ The list of hooks is quite common but here it is as a reminder:
169
+
170
+ - `before :validation`
171
+ - `after :validation`
172
+ - `before :create`
173
+ - `after :create`
174
+ - `before :update`
175
+ - `after :update`
176
+ - `before :save` (both create or update)
177
+ - `after :save` (both create or update)
178
+ - `before :delete`
179
+ - `after :delete`
180
+
181
+ Now you can register many callbacks for the same hook. They will be chained in
182
+ the order you register them. However, if for any reason you need to register a
183
+ callback and make sure it runs before the others, you can add `prepend: true`.
184
+
185
+ ```ruby
186
+ before :save, prepend: true do
187
+ puts 'Shotgun !!!'
188
+ end
189
+ ```
190
+
191
+ If you want to go even further and create your own hooks, this is very easy.
192
+ You can create a hook like this:
193
+
194
+ ```ruby
195
+ document.exec_callback(:my_hook)
196
+ ```
197
+
198
+ And you would then register a callback like this:
199
+
200
+ ```ruby
201
+ register_callback :my_hook do
202
+ # Do something...
203
+ end
204
+ ```
205
+
206
+ You can use `before` and `after` as well. In fact this:
207
+
208
+ ```ruby
209
+ after :lunch do
210
+ # Do something...
211
+ end
212
+ ```
213
+
214
+ Is equivalent to:
215
+
216
+ ```ruby
217
+ register_callback :after_lunch do
218
+ # Do something...
219
+ end
220
+ ```
221
+
222
+ ### Single Documents
223
+
224
+ Sometimes you want a collection with only one document, like for recording
225
+ settings for example. In this case you can use the `::is_unique` class method.
226
+
227
+ ```ruby
228
+ require 'populate_me/document'
229
+
230
+ class GeneralWebsiteSettings < PopulateMe::Document
231
+ field :main_meta_title
232
+ field :main_meta_description
233
+ field :google_analytics_ref
234
+ end
235
+
236
+ GeneralWebsiteSettings.is_unique
237
+ ```
238
+
239
+ It just creates the document if it does not exist yet with the ID `unique`.
240
+ If you want a different ID, you can pass it as an argument.
241
+
242
+ Just make sure that if you have fields with `required: true`, they also have
243
+ a `:default` value. Otherwise the creation of the document will fail because it
244
+ is not `self.valid?`.
245
+
246
+ ### Mongo Documents
247
+
248
+ Note: the current version works with the mongo driver version 2
249
+
250
+ Now let's declare a real document class which can persist on a database,
251
+ the `MongoDB` kind of document. The first thing we need to clarify is the
252
+ setup. Here is a classic setup:
253
+
254
+ ```ruby
255
+ # lib/db.rb
256
+ require 'mongo'
257
+ require 'populate_me/mongo'
258
+
259
+ client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'your-database-name')
260
+
261
+ PopulateMe::Mongo.set :db, client.database
262
+
263
+ require 'person'
264
+ ```
265
+
266
+ Then the document is pretty much the same as the prototype except that it
267
+ subclasses `PopulateMe::Mongo` instead.
268
+
269
+ ```ruby
270
+ # lib/person.rb
271
+ require 'populate_me/mongo'
272
+
273
+ class Person < PopulateMe::Mongo
274
+ field :firstname
275
+ field :lastname
276
+ end
277
+ ```
278
+
279
+ As you can see in setup, you can define inheritable settings on
280
+ `PopulateMe::Mongo`, meaning that any subclass after this will have the `:db`
281
+ and you can set it only once.
282
+
283
+ Nevertheless it is obviously possible to set a different `:db` for each class.
284
+
285
+ ```ruby
286
+ # lib/person.rb
287
+ require 'populate_me/mongo'
288
+
289
+ class Person < PopulateMe::Mongo
290
+
291
+ set :db, $my_db
292
+
293
+ field :firstname
294
+ field :lastname
295
+
296
+ end
297
+ ```
298
+
299
+ This is particularly useful if you keep a type of documents in a different
300
+ location for example. Otherwise it is more convenient to set it once
301
+ and for all.
302
+
303
+ You can also set `:collection_name`, but in most cases you would let `PopulateMe`
304
+ defaults it to the dasherized class name. So `BlogArticle::Comment` would be
305
+ in the collection called `blog-article--comment`.
306
+
307
+ Whatever you choose, you will have access to the collection object with the
308
+ `::collection` class method. Which allows you to do anything the driver does.
309
+
310
+ ```ruby
311
+ first_pedro = Person.collection.find({ 'firstname' => 'Pedro' }).first
312
+ mcs = Person.collection.find({ 'lastname' => /^Mc/i })
313
+ ```
314
+
315
+ Although since these are methods from the driver, `first_pedro` returns a hash,
316
+ and `mcs` returns a `Mongo::Collection::View`. If you want document object, you can use
317
+ the `::cast` class method which takes a block in the class context/scope and
318
+ casts either a single hash into a full featured document, or casts the items of
319
+ an array (or anything which responds to `:map`).
320
+
321
+ ```ruby
322
+ first_pedro = Person.cast{ collection.find_one({ 'firstname' => 'Pedro' }) }
323
+ mcs = Person.cast{ collection.find({ 'lastname' => /^Mc/i }) }
324
+ first_pedro.class # returns Person
325
+ mcs[0].class # returns Person
326
+ ```
327
+
328
+ Admin
329
+ -----
330
+
331
+ A basic admin would look like this:
332
+
333
+ ```ruby
334
+ # lib/admin.rb
335
+ require "populate_me/admin"
336
+
337
+ class Admin < PopulateMe::Admin
338
+ # Since we are in lib we use this to move
339
+ # the root one level up.
340
+ # Not mandatory but useful if you plan to have
341
+ # custom views in the main views folder
342
+ set :root, ::File.expand_path('../..', __FILE__)
343
+ # Only if you use Rack::Cerberus for authentication
344
+ # you can pass the settings
345
+ set :cerberus, {company_name: 'Nintendo'}
346
+ # Build menu and sub-menus
347
+ set :menu, [
348
+ ['Settings', '/admin/form/settings/unique'],
349
+ ['Articles', '/admin/list/article'],
350
+ ['Staff', [
351
+ ['Designers', '/admin/list/staff-member?filter[job]=Designer'],
352
+ ['Developers', '/admin/list/staff-member?filter[job]=Developer'],
353
+ ]]
354
+ ]
355
+ end
356
+ ```
357
+
358
+ So the main thing you need is to define your menu. Then mount it in
359
+ your `config.ru` whereever you want.
360
+
361
+ ```ruby
362
+ # config.ru
363
+ require 'admin'
364
+
365
+ map '/admin' do
366
+ run Admin
367
+ end
368
+ ```
369
+
370
+ Most of the URLs in your menu will probably be for the admin itself and use
371
+ the admin URL patterns, but this is not mandatory. A link to an external page
372
+ would load in a new tab. Whereas admin URLs create columns in the `PopulateMe`
373
+ user interface. Many things are possible with these patterns but here are
374
+ the main ones:
375
+
376
+ - `/:path_to_admin/list/:dasherized_document_class` This gives you the list of
377
+ documents from the desired class. They are ordered as specified by `sort_by`.
378
+ You can also filter like in the example to get only specific documents.
379
+ - `:path_to_admin/form/:dasherized_document_class/:id` You would rarely use this
380
+ one which directly opens the form of a specific document, since all this is
381
+ generally accessed from the list page. It doesn't need to be coded. The only is
382
+ probably for [single documents](#single-documents) because they are not part of
383
+ a list. The ID would then be litterally `unique`, or whatever ID you declared
384
+ instead.
385
+
386
+ API
387
+ ---
388
+
389
+ In a normal use, you most likely don't have anything to do with the `API` module.
390
+ It is just another middleware automatically mounted under `/api` on your `Admin`.
391
+ So if your `Admin` path is `/admin`, then your `API` path is `/admin/api`.
392
+
393
+ The purpose of the `API` module is to provide all the path patterns for creating,
394
+ deleting and updating documents. The interface does all the job for you. But if
395
+ you end up building your all custom interface, you probably want to [have a look
396
+ at the implementation](lib/populate_me/api.rb).
397
+
398
+ Another aspect of the `API` is that it relies on document methods. So if you
399
+ want to create a subclass of `Document`, make sure that you override everything
400
+ that the `API` or the `Admin` may need.
401
+
402
+ 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
403
+ of this Gem.
9
404
 
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.pattern = 'test/test_*.rb'
8
+ unless ENV['TESTONLY'].nil?
9
+ t.pattern = t.pattern.sub(/\*/, ENV['TESTONLY'])
10
+ end
11
+ t.options = '--pride'
12
+ t.warning = false
13
+ end
14
+