populate-me 0.0.33 → 0.1.0

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