rear 0.2.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 (121) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +20 -0
  5. data/LICENSE +19 -0
  6. data/README.md +101 -0
  7. data/Rakefile +79 -0
  8. data/assets/api.js +307 -0
  9. data/assets/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css +8 -0
  10. data/assets/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js +26 -0
  11. data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  12. data/assets/bootstrap/css/bootstrap.min.css +9 -0
  13. data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  14. data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  15. data/assets/bootstrap/js/bootstrap.min.js +6 -0
  16. data/assets/jquery.js +5 -0
  17. data/assets/noty/jquery.noty.js +520 -0
  18. data/assets/noty/layouts/top.js +34 -0
  19. data/assets/noty/layouts/topRight.js +43 -0
  20. data/assets/noty/promise.js +432 -0
  21. data/assets/noty/themes/default.js +156 -0
  22. data/assets/select2-bootstrap.css +86 -0
  23. data/assets/select2/select2-spinner.gif +0 -0
  24. data/assets/select2/select2.css +652 -0
  25. data/assets/select2/select2.min.js +22 -0
  26. data/assets/select2/select2.png +0 -0
  27. data/assets/select2/select2x2.png +0 -0
  28. data/assets/ui.css +75 -0
  29. data/assets/xhr.js +4 -0
  30. data/bin/rear +65 -0
  31. data/docs/Assocs.md +100 -0
  32. data/docs/Columns.md +404 -0
  33. data/docs/Deploy.md +62 -0
  34. data/docs/FileManager.md +75 -0
  35. data/docs/Filters.md +341 -0
  36. data/docs/Setup.md +201 -0
  37. data/lib/rear.rb +13 -0
  38. data/lib/rear/actions.rb +98 -0
  39. data/lib/rear/constants.rb +61 -0
  40. data/lib/rear/controller_setup.rb +249 -0
  41. data/lib/rear/helpers.rb +17 -0
  42. data/lib/rear/helpers/class.rb +46 -0
  43. data/lib/rear/helpers/columns.rb +68 -0
  44. data/lib/rear/helpers/filters.rb +147 -0
  45. data/lib/rear/helpers/generic.rb +73 -0
  46. data/lib/rear/helpers/order.rb +47 -0
  47. data/lib/rear/helpers/pager.rb +35 -0
  48. data/lib/rear/helpers/render.rb +37 -0
  49. data/lib/rear/home_controller.rb +10 -0
  50. data/lib/rear/input.rb +341 -0
  51. data/lib/rear/orm.rb +73 -0
  52. data/lib/rear/rear.rb +74 -0
  53. data/lib/rear/setup.rb +9 -0
  54. data/lib/rear/setup/associations.rb +33 -0
  55. data/lib/rear/setup/columns.rb +109 -0
  56. data/lib/rear/setup/filters.rb +314 -0
  57. data/lib/rear/setup/generic.rb +59 -0
  58. data/lib/rear/setup/menu.rb +39 -0
  59. data/lib/rear/templates/editor/ace.slim +7 -0
  60. data/lib/rear/templates/editor/assocs.slim +10 -0
  61. data/lib/rear/templates/editor/boolean.slim +5 -0
  62. data/lib/rear/templates/editor/bulk_edit.slim +75 -0
  63. data/lib/rear/templates/editor/checkbox.slim +5 -0
  64. data/lib/rear/templates/editor/ckeditor.slim +7 -0
  65. data/lib/rear/templates/editor/date.slim +9 -0
  66. data/lib/rear/templates/editor/datetime.slim +9 -0
  67. data/lib/rear/templates/editor/layout.slim +103 -0
  68. data/lib/rear/templates/editor/password.slim +1 -0
  69. data/lib/rear/templates/editor/radio.slim +5 -0
  70. data/lib/rear/templates/editor/select.slim +3 -0
  71. data/lib/rear/templates/editor/string.slim +1 -0
  72. data/lib/rear/templates/editor/text.slim +1 -0
  73. data/lib/rear/templates/editor/time.slim +9 -0
  74. data/lib/rear/templates/error.slim +36 -0
  75. data/lib/rear/templates/filters/boolean.slim +10 -0
  76. data/lib/rear/templates/filters/checkbox.slim +15 -0
  77. data/lib/rear/templates/filters/date.slim +10 -0
  78. data/lib/rear/templates/filters/datetime.slim +10 -0
  79. data/lib/rear/templates/filters/layout.slim +26 -0
  80. data/lib/rear/templates/filters/radio.slim +14 -0
  81. data/lib/rear/templates/filters/select.slim +9 -0
  82. data/lib/rear/templates/filters/string.slim +3 -0
  83. data/lib/rear/templates/filters/text.slim +3 -0
  84. data/lib/rear/templates/filters/time.slim +10 -0
  85. data/lib/rear/templates/home.slim +0 -0
  86. data/lib/rear/templates/layout.slim +78 -0
  87. data/lib/rear/templates/pager.slim +22 -0
  88. data/lib/rear/templates/pane/ace.slim +2 -0
  89. data/lib/rear/templates/pane/assocs.slim +62 -0
  90. data/lib/rear/templates/pane/boolean.slim +2 -0
  91. data/lib/rear/templates/pane/checkbox.slim +5 -0
  92. data/lib/rear/templates/pane/ckeditor.slim +2 -0
  93. data/lib/rear/templates/pane/date.slim +2 -0
  94. data/lib/rear/templates/pane/datetime.slim +2 -0
  95. data/lib/rear/templates/pane/layout.slim +111 -0
  96. data/lib/rear/templates/pane/password.slim +2 -0
  97. data/lib/rear/templates/pane/quickview.slim +21 -0
  98. data/lib/rear/templates/pane/radio.slim +5 -0
  99. data/lib/rear/templates/pane/select.slim +5 -0
  100. data/lib/rear/templates/pane/string.slim +2 -0
  101. data/lib/rear/templates/pane/text.slim +2 -0
  102. data/lib/rear/templates/pane/time.slim +2 -0
  103. data/lib/rear/utils.rb +288 -0
  104. data/rear.gemspec +27 -0
  105. data/test/helpers.rb +33 -0
  106. data/test/models/ar.rb +52 -0
  107. data/test/models/dm.rb +53 -0
  108. data/test/models/sq.rb +58 -0
  109. data/test/setup.rb +4 -0
  110. data/test/templates/adhoc/book/editor/name.slim +1 -0
  111. data/test/templates/adhoc/book/editor/string.slim +1 -0
  112. data/test/templates/adhoc/book/pane/name.slim +1 -0
  113. data/test/templates/adhoc/book/pane/string.slim +1 -0
  114. data/test/templates/shared/shared-templates/editor/string.slim +1 -0
  115. data/test/templates/shared/shared-templates/pane/string.slim +1 -0
  116. data/test/test__assocs.rb +249 -0
  117. data/test/test__columns.rb +269 -0
  118. data/test/test__crud.rb +81 -0
  119. data/test/test__custom_templates.rb +147 -0
  120. data/test/test__filters.rb +228 -0
  121. metadata +220 -0
@@ -0,0 +1,10 @@
1
+ class RearHomeController < E
2
+ RearControllerSetup.init(self)
3
+
4
+ map '/'
5
+ label :Home
6
+
7
+ def get_index
8
+ reander_l(:layout) { reander_p(:home) }
9
+ end
10
+ end
data/lib/rear/input.rb ADDED
@@ -0,0 +1,341 @@
1
+ class RearInput
2
+ include RearConstants
3
+ include RearUtils
4
+
5
+ attr_reader :name, :string_name, :type
6
+ attr_reader :dom_id, :css_class
7
+
8
+ def initialize name, type = COLUMNS__DEFAULT_TYPE, attrs = {}, brand_new_item = nil, &proc
9
+ @name, @string_name, @type = name, name.to_s, type
10
+ @brand_new_item = brand_new_item
11
+ @dom_id = ['Rear', 'Input', 'DOMIDFor', @string_name.capitalize.gsub(/\W/, ''), __id__].join
12
+ @css_class = 'CSSClassFor' << @dom_id
13
+
14
+ @pane_template = pane_template @type
15
+ @pane_value = proc do
16
+ val = item[column.name]
17
+ if val.is_a?(String)
18
+ val.size > COLUMNS__PANE_MAX_LENGTH ?
19
+ val[0..COLUMNS__PANE_MAX_LENGTH] + ' ...' : val
20
+ else
21
+ val
22
+ end
23
+ end
24
+
25
+ @editor_template = editor_template @type
26
+ @editor_value = proc { item[column.name] }
27
+
28
+ @active_options = proc { item[column.name] }
29
+
30
+ @html_attrs = Hash[[:*, :pane, :editor].map {|s| [s, {}]}]
31
+ html_attrs(attrs)
32
+ self.instance_exec(&proc) if proc
33
+ end
34
+
35
+ def name?
36
+ return if disabled?
37
+ return if readonly?
38
+ multiple? || checkbox? ? '%s[]' % name : name.to_s
39
+ end
40
+
41
+ def label label = nil
42
+ @label = label if label
43
+ @label
44
+ end
45
+
46
+ def row row = nil
47
+ @row = row.to_s if row
48
+ @row
49
+ end
50
+
51
+ def row?
52
+ @row
53
+ end
54
+
55
+ # set HTML attributes to be used on current column on both pane and editor pages
56
+ # @note will override any attrs set globally via `html_attrs` at class level
57
+ def html_attrs attrs = {}
58
+ set_html_attrs(attrs) if attrs.any?
59
+ @html_attrs[:*] || {}
60
+ end
61
+ alias attrs html_attrs
62
+
63
+ # set HTML attributes to be used on current column on pane pages
64
+ # @note will override any attrs set globally via `pane_attrs` or `html_attrs` at class level
65
+ def pane_attrs attrs = {}
66
+ set_html_attrs(attrs, :pane) if attrs.any?
67
+ pane_attrs? || html_attrs
68
+ end
69
+
70
+ def pane_attrs?
71
+ (a = @html_attrs[:pane]) && a.any? && a
72
+ end
73
+
74
+ # set HTML attributes to be used on current column on editor pages
75
+ # @note will override any attrs set globally via `editor_attrs` or `html_attrs` at class level
76
+ def editor_attrs attrs = {}
77
+ set_html_attrs(attrs, :editor) if attrs.any?
78
+ editor_attrs? || html_attrs
79
+ end
80
+
81
+ def editor_attrs?
82
+ (a = @html_attrs[:editor]) && a.any? && a
83
+ end
84
+
85
+ def pane_template template = nil, &proc
86
+ caller.each {|l| puts l} if template == :integer
87
+ if template
88
+ @pane_template = 'pane/%s.slim' % template
89
+ elsif template == false
90
+ @pane_template = nil
91
+ end
92
+ @pane_template = proc if proc
93
+ @pane_template
94
+ end
95
+ alias pane pane_template
96
+
97
+ def pane_value &proc
98
+ @pane_value = proc if proc
99
+ @pane_value
100
+ end
101
+
102
+ def editor_template template = nil, &proc
103
+ if template
104
+ @editor_template = 'editor/%s.slim' % template
105
+ elsif template == false
106
+ @editor_template = nil
107
+ end
108
+ @editor_template = proc if proc
109
+ @editor_template
110
+ end
111
+ alias editor editor_template
112
+
113
+ def editor_value &proc
114
+ @editor_value = proc if proc
115
+ @editor_value
116
+ end
117
+
118
+ def value &proc
119
+ pane_value &proc
120
+ editor_value &proc
121
+ end
122
+
123
+ # required on :radio, :checkbox and :select columns.
124
+ # options provided as Array or Hash
125
+ # use an Array when keys are the same as values.
126
+ # use a Hash when keys are different from values.
127
+ # if block given it should return an Array of selected keys or a single key.
128
+ # if block not given, no options will be selected.
129
+ #
130
+ # @example use an Array. will send 'Orange' to ORM
131
+ #
132
+ # column :fruit, :select do
133
+ # options('Apple', 'Orange', 'Peach') { 'Orange' }
134
+ # end
135
+ #
136
+ #
137
+ # @example use a Hash. will send '2' to ORM
138
+ #
139
+ # column :fruit, :select do
140
+ # options(1 => 'Apple', 2 => 'Orange', 3 => 'Peach') { 2 }
141
+ # end
142
+ #
143
+ # @note when using :checkbox type or :select type with :multiple option,
144
+ # Espresso will send an Array of keys to your ORM.
145
+ # usually ORM will handle received array automatically,
146
+ # however, if you want to send a string rather than array,
147
+ # use `before` callback to coerce the array into string.
148
+ #
149
+ # @example this example will send ['Orange', 'Peach'] Array
150
+ # which will be caught by `before :save` callback
151
+ # and coerced into string
152
+ #
153
+ # before :save do
154
+ # params[:fruit] = params[:fruit].join(',')
155
+ # end
156
+ # column :fruit, :select, :multiple => true do
157
+ # options('Apple', 'Orange', 'Peach') { ['Orange', 'Peach'] }
158
+ # end
159
+ #
160
+ # @example will send ['2', '3'] Array to your ORM
161
+ # column :fruit, :checkbox do
162
+ # options(1 => 'Apple', 2 => 'Orange', 3 => 'Peach') { [2, 3] }
163
+ # end
164
+ #
165
+ def options *args, &proc
166
+ return @options || {} if args.empty?
167
+ @options = args.inject({}) do |f,c|
168
+ c.is_a?(Hash) ? f.merge(c) : f.merge(c => c)
169
+ end
170
+ @active_options = proc if proc
171
+ end
172
+
173
+ def active_options
174
+ @active_options
175
+ end
176
+
177
+ def optioned?
178
+ select? || checkbox? || radio?
179
+ end
180
+
181
+ def pane?
182
+ @pane_template
183
+ end
184
+
185
+ def editor?
186
+ @editor_template
187
+ end
188
+
189
+ def readonly!
190
+ @readonly = true
191
+ end
192
+
193
+ def readonly?
194
+ return if @brand_new_item
195
+ @readonly
196
+ end
197
+
198
+ def disable!
199
+ @disabled = true
200
+ end
201
+ alias disabled! disable!
202
+
203
+ def disabled?
204
+ @disabled
205
+ end
206
+
207
+ def multiple!
208
+ @multiple = true
209
+ end
210
+
211
+ def multiple?
212
+ @multiple
213
+ end
214
+
215
+ def checkbox?
216
+ type == :checkbox
217
+ end
218
+
219
+ def select?
220
+ type == :select
221
+ end
222
+
223
+ def radio?
224
+ type == :radio
225
+ end
226
+
227
+ def textual?
228
+ type == :text || type == :rte
229
+ end
230
+
231
+ def boolean?
232
+ type == :boolean
233
+ end
234
+
235
+ # when ordering by some column, "ORDER BY" will use only the selected column.
236
+ # this column-specific setup allow to order returned items by multiple columns.
237
+ #
238
+ # # @example
239
+ # class News
240
+ # include DataMapper::Resource
241
+ #
242
+ # property :id, Serial
243
+ # property :name, String
244
+ # property :date, Date
245
+ # property :status, Integer
246
+ # end
247
+ #
248
+ # Rear.register News do
249
+ # input :date do
250
+ # order_by :date, :id
251
+ # end
252
+ # end
253
+ #
254
+ # this method are also useful when you need to sort items by a "decorative" column,
255
+ # meant a column that does not exists in db but you need it on pane pages
256
+ # to render more informative rows.
257
+ # For ex. display category of each article when rendering articles
258
+ #
259
+ # # @example
260
+ # class Article
261
+ # include DataMapper::Resource
262
+ # # ...
263
+ #
264
+ # belongs_to :category
265
+ # end
266
+ #
267
+ # Rear.register do
268
+ # # defining a "decorative" column to display categories of each article
269
+ # input :Categories do
270
+ # pane { item.categories.map {|c| c.name}.join(', ') }
271
+ # # when Categories column clicked on pane page,
272
+ # # we want articles to be sorted by category id
273
+ # order_by :category_id
274
+ # end
275
+ # end
276
+ #
277
+ # @note do not pass ordering vector when setting costom `order_by` for columns.
278
+ # vector will be added automatically, so pass only column names.
279
+ # if vector passed, ordering will broke badly.
280
+ #
281
+ def order_by *columns
282
+ @order_by = columns if columns.any?
283
+ @order_by || [name]
284
+ end
285
+
286
+ def order_by?
287
+ @order_by
288
+ end
289
+
290
+ # allow to define a list of snippets you need to insert into edited content.
291
+ # relevant only on :ace / :ckeditor columns.
292
+ def snippets *snippets, &proc
293
+ snippets.any? && @snippets = snippets
294
+ proc && @snippets = proc
295
+ @snippets
296
+ end
297
+
298
+ # various opts for CKEditor
299
+ #
300
+ # @param [Hash] opts
301
+ # @option opts :path
302
+ # physical path to folder containing images/videos to be picked up by file browser
303
+ # @option opts :prefix
304
+ # file browser will build URL to file/video by extracting
305
+ # path(set via `:path` option) from full path to file.
306
+ # that's it, if path is "/foo/bar"
307
+ # and full path to file is "/foo/bar/baz/image.jpg",
308
+ # the URL used in browser will be "/baz/image.jpg".
309
+ # `:prefix` option allow to define a string to be prepended to "/baz/image.jpg".
310
+ # @option opts :lang
311
+ # localizing CKEditors
312
+ def ckeditor opts = {}
313
+ @ckeditor_opts = opts
314
+ end
315
+ def ckeditor_opts; @ckeditor_opts || {}; end
316
+
317
+ private
318
+ def set_html_attrs html_attrs, scope = :*
319
+ html_attrs = Hash[@html_attrs[scope].merge(html_attrs)]
320
+
321
+ html_attrs.each_key do |k|
322
+ readonly! if k == :readonly
323
+ disabled! if k == :disabled
324
+ multiple! if k == :multiple
325
+ end
326
+ html_attrs.delete(:readonly) if @brand_new_item
327
+
328
+ html_attrs.delete(:pane) == false && pane(false)
329
+ html_attrs.delete(:editor) == false && editor(false)
330
+
331
+ unless @label = html_attrs.delete(:label)
332
+ @label = name.to_s.gsub(/_/, ' ')
333
+ @label.capitalize! unless name == :id
334
+ @label << '?' if type == :boolean
335
+ end
336
+ @row = html_attrs.delete(:row)
337
+
338
+ @html_attrs[scope] = normalize_html_attrs(html_attrs)
339
+ end
340
+
341
+ end
data/lib/rear/orm.rb ADDED
@@ -0,0 +1,73 @@
1
+ class RearORM
2
+ include RearUtils
3
+
4
+ attr_reader :model, :pkey
5
+
6
+ def initialize model, pkey = :id
7
+ @model, @pkey, @orm = model, pkey, orm(model)
8
+ end
9
+
10
+ def [] id
11
+ sequel? ?
12
+ model[id] :
13
+ model.first(conditions: {pkey => id})
14
+ end
15
+
16
+ def count conditions = {}
17
+ if sequel?
18
+ sequel_dataset(model, conditions).count
19
+ else
20
+ model.count(conditions)
21
+ end
22
+ end
23
+
24
+ def filter conditions = {}
25
+ if sequel?
26
+ sequel_dataset(model, conditions).all
27
+ else
28
+ model.all(conditions)
29
+ end
30
+ end
31
+
32
+ def assoc_filter assoc, item, conditions
33
+ if sequel?
34
+ sequel_dataset(item.send('%s_dataset' % assoc), conditions).all
35
+ else
36
+ result = item.send(assoc, conditions)
37
+ result.respond_to?(:size) ? result : [result].compact
38
+ end
39
+ end
40
+
41
+ def assoc_count assoc, item, conditions
42
+ if sequel?
43
+ sequel_dataset(item.send('%s_dataset' % assoc), conditions).count
44
+ else
45
+ if result = item.send(assoc)
46
+ result.respond_to?(:count) ? result.count(conditions) : 1
47
+ else
48
+ 0
49
+ end
50
+ end
51
+ end
52
+
53
+ def delete_multiple *ids
54
+ ids.flatten!
55
+ model.destroy(ids) if @orm == :ar
56
+ model.all(pkey => ids).destroy! if @orm == :dm
57
+ model.filter(pkey => ids).destroy if @orm == :sq
58
+ end
59
+
60
+ def sequel?; @orm == :sq end
61
+
62
+ def sequel_dataset dataset, conditions = {}
63
+ filters, limit, offset, order =
64
+ conditions.values_at(:conditions, :limit, :offset, :order)
65
+ ds = limit ? dataset.limit(*[limit, offset].compact) : dataset
66
+ ds.filter(filters || {}).order(*[order].compact)
67
+ end
68
+
69
+ def singularize smth
70
+ model.send(:singularize, smth) if sequel?
71
+ end
72
+
73
+ end
data/lib/rear/rear.rb ADDED
@@ -0,0 +1,74 @@
1
+ module Rear
2
+ class << self
3
+ MODELS, CONTROLLERS = {}, []
4
+
5
+ def register *models, &proc
6
+ models.flatten.each do |model|
7
+ (MODELS[model] ||= []).push(proc)
8
+ end
9
+ end
10
+ alias setup register
11
+
12
+ def included base
13
+ if EUtils.is_app?(base)
14
+ RearControllerSetup.init(base)
15
+ CONTROLLERS << base
16
+ else
17
+ raise ArgumentError, '%s is not a Espresso controller' % base
18
+ end
19
+ end
20
+
21
+ def controllers
22
+ @controllers ||= begin
23
+ MODELS.each_pair do |model,procs|
24
+ model = RearUtils.extract_constant(model)
25
+ controller = RearUtils.initialize_model_controller(model)
26
+ procs.compact.each {|proc| controller.class_exec(model, &proc)}
27
+ CONTROLLERS << controller
28
+ end
29
+ CONTROLLERS.uniq.each do |controller|
30
+ controller.model && controller.assocs.each_value do |assocs|
31
+ assocs.each_value do |assoc|
32
+ if remote_model = assoc[:remote_model]
33
+ CONTROLLERS << RearUtils.associated_model_controller(remote_model)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ MODELS.clear.freeze
39
+ CONTROLLERS.unshift RearHomeController
40
+ CONTROLLERS.uniq!
41
+ CONTROLLERS.freeze
42
+ end
43
+ end
44
+
45
+ def menu
46
+ @menu ||= begin
47
+ containers = {}
48
+ controllers = Rear.controllers.reject {|c| c == RearHomeController}.
49
+ select {|c| c.label}.inject({}) do |f,c|
50
+ c.menu_group? ?
51
+ ((containers[c.menu_group] ||= []).push(c); f) : f.merge(c=>[c])
52
+ end
53
+ controllers.merge(containers).sort do |a,b|
54
+ b.last.inject(0) {|t,c| t += c.position} <=> a.last.inject(0) {|t,c| t += c.position}
55
+ end
56
+ end
57
+ end
58
+
59
+ def app
60
+ @app ||= E.new.mount(controllers)
61
+ end
62
+ alias to_app app
63
+ alias mount! app
64
+
65
+ def call env
66
+ app.call env
67
+ end
68
+
69
+ def run *args
70
+ app.run *args
71
+ end
72
+
73
+ end
74
+ end