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
data/lib/rear/setup.rb ADDED
@@ -0,0 +1,9 @@
1
+ module RearSetup
2
+ include RearConstants
3
+ end
4
+
5
+ require 'rear/setup/associations'
6
+ require 'rear/setup/columns'
7
+ require 'rear/setup/filters'
8
+ require 'rear/setup/generic'
9
+ require 'rear/setup/menu'
@@ -0,0 +1,33 @@
1
+ module RearSetup
2
+
3
+ # make some assocs readonly.
4
+ # this is a cosmetic measure - frontend just wont let user modify them
5
+ # but the API for their manipulation will still work
6
+ def readonly_assocs *assocs
7
+ (@__rear__readonly_assocs ||= []).concat(assocs) if assocs.any?
8
+ (@__rear__readonly_assocs || [])
9
+ end
10
+ alias readonly_assoc readonly_assocs
11
+
12
+ # ignore some assocs.
13
+ # this is a cosmetic measure - assocs just wont be displayed on frontend
14
+ # but the API for their manipulation will still work
15
+ def ignored_assocs *assocs
16
+ (@__rear__ignored_assocs ||= []).concat(assocs) if assocs.any?
17
+ (@__rear__ignored_assocs || [])
18
+ end
19
+ alias ignored_assoc ignored_assocs
20
+ alias ignore_assocs ignored_assocs
21
+ alias ignore_assoc ignored_assocs
22
+
23
+ # when rendering some model in a "remote" association pane,
24
+ # all columns of current model will be displayed.
25
+ #
26
+ # `assoc_columns` allow to set a list of "remotely" displayed columns.
27
+ #
28
+ def assoc_columns *columns
29
+ @__rear__assoc_columns = columns if columns.any?
30
+ @__rear__assoc_columns
31
+ end
32
+
33
+ end
@@ -0,0 +1,109 @@
1
+ module RearSetup
2
+
3
+ # add new column or override automatically added one
4
+ #
5
+ # @param [Symbol] name
6
+ # @param [Symbol] type one of [:string, :text, :date, :time, :datetime, :boolean]
7
+ # default: :string
8
+ # @param [Hash] opts_and_or_html_attrs
9
+ # @option opts_and_or_html_attrs :pane
10
+ # when set to false the column wont be displayed on pane pages
11
+ # @option opts_and_or_html_attrs :editor
12
+ # when set to false the column wont be displayed on editor pages
13
+ # @option opts_and_or_html_attrs :label
14
+ # @option opts_and_or_html_attrs :readonly
15
+ # @option opts_and_or_html_attrs :disabled
16
+ # @option opts_and_or_html_attrs :multiple
17
+ # @option opts_and_or_html_attrs any attributes to be added to HTML tag
18
+ #
19
+ # @example
20
+ # input :name
21
+ # # => <input type="text" value="...">
22
+ #
23
+ # @example
24
+ # input :name, :style => "width: 100%;"
25
+ # # => <input style="width: 100%;" type="text" ...>
26
+ #
27
+ # @example
28
+ # input :name, :text, :cols => 40
29
+ # # => <textarea cols="40" ...>...</textarea>
30
+ #
31
+ # @example display author name only on pane pages
32
+ # input(:author_id) { disable :editor }
33
+ #
34
+ # @example Ace Editor
35
+ # input :content, :ace
36
+ #
37
+ # @example CKEditor
38
+ # input :content, :ckeditor
39
+ #
40
+ def input name, type = nil, opts_and_or_html_attrs = {}, &proc
41
+
42
+ type.is_a?(Hash) && (opts_and_or_html_attrs = type) && (type = nil)
43
+ opts_and_or_html_attrs[:row] = opts_and_or_html_attrs[:row] ?
44
+ opts_and_or_html_attrs[:row].to_s : @__rear__row
45
+
46
+ existing_column = nil
47
+ columns.each_with_index {|c,i| c && c.first == name && existing_column = [c,i]}
48
+ column = existing_column ? Array.new(existing_column.first) : []
49
+
50
+ column[0] = name
51
+ column[1] = type ? type.to_s.downcase.to_sym : column[1] || COLUMNS__DEFAULT_TYPE
52
+ column[2] = (column[2]||{}).merge(opts_and_or_html_attrs).freeze
53
+ column[3] = proc
54
+ column.freeze
55
+
56
+ existing_column ?
57
+ columns[existing_column.last] = column :
58
+ columns << column
59
+ end
60
+
61
+ # reset any automatically(or manually) added columns
62
+ def reset_columns!
63
+ @__rear__columns = {}
64
+ end
65
+
66
+ # display multiple columns in a row(on editor)
67
+ #
68
+ # @example using a block
69
+ #
70
+ # row :Location do
71
+ # column :country
72
+ # column :state
73
+ # column :city
74
+ # end
75
+ #
76
+ # @example without a block
77
+ #
78
+ # column :country, :row => :Location
79
+ # column :state, :row => :Location
80
+ # column :city, :row => :Location
81
+ #
82
+ def row label = nil, &proc
83
+ # explicit labels will be strings and implicit ones will be numbers
84
+ # as a way to distinguish them when rendering templates
85
+ @__rear__row = label ? label.to_s : (Time.now.to_f + rand)
86
+ self.instance_exec(&proc) if proc
87
+ @__rear__row = nil
88
+ end
89
+
90
+ # set HTML attributes to be used on all columns on both pane and editor pages
91
+ def html_attrs attrs = {}
92
+ @__rear__html_attrs = attrs if attrs.any? && @__rear__html_attrs.nil?
93
+ @__rear__html_attrs || {}
94
+ end
95
+
96
+ # set HTML attributes to be used on all columns only on pane pages.
97
+ # @note will override any attrs set via `html_attrs`
98
+ def pane_attrs attrs = {}
99
+ @__rear__pane_attrs = attrs if attrs.any? && @__rear__pane_attrs.nil?
100
+ @__rear__pane_attrs || html_attrs
101
+ end
102
+
103
+ # set HTML attributes to be used on all columns only on editor pages.
104
+ # @note will override any attrs set via `html_attrs`
105
+ def editor_attrs attrs = {}
106
+ @__rear__editor_attrs = attrs if attrs.any? && @__rear__editor_attrs.nil?
107
+ @__rear__editor_attrs || html_attrs
108
+ end
109
+ end
@@ -0,0 +1,314 @@
1
+ module RearSetup
2
+
3
+ # add a filter.
4
+ #
5
+ # by default a text filter will be rendered.
6
+ # to define filters of another type, pass desired type as a Symbol via second argument.
7
+ #
8
+ # acceptable types:
9
+ # - :string/:text
10
+ # - :select
11
+ # - :radio
12
+ # - :checkbox
13
+ # - :date
14
+ # - :datetime
15
+ # - :time
16
+ # - :boolean
17
+ #
18
+ # @note comparison function
19
+ # :text/:string filters will use :like comparison function by default:
20
+ # ... WHERE name LIKE '%VALUE%' ...
21
+ #
22
+ # :checkbox filters will use :in comparison function by default:
23
+ # ... WHERE column IN ('VALUE1', 'VALUE2') ...
24
+ # if you use a custom cmp function with a :checkbox filter,
25
+ # filter's column will be compared to each selected value:
26
+ # ... WHERE (column LIKE '%VALUE1%' OR column LIKE '%VALUE2%') ...
27
+ #
28
+ # any other types will use :eql comparison function by default:
29
+ # "... WHERE created_at = 'VALUE' ...
30
+ #
31
+ # to use a non-default comparison function, set it via :cmp option:
32
+ # `filter :name, :cmp => :eql`
33
+ #
34
+ # available comparison functions:
35
+ # - :eql # equal
36
+ # - :not # not equal
37
+ # - :gt # greater than
38
+ # - :gte # greater than or equal
39
+ # - :lt # less than
40
+ # - :lte # less than or equal
41
+ # - :like # - column LIKE '%VALUE%'
42
+ # - :unlike # - column NOT LIKE '%VALUE%'
43
+ # - :_like # match beginning of line - column LIKE 'VALUE%'
44
+ # - :_unlike # - column NOT LIKE 'VALUE%'
45
+ # - :like_ # match end of line - column LIKE '%VALUE'
46
+ # - :unlike_ # - column NOT LIKE '%VALUE'
47
+ # - :_like_ # exact match - column LIKE 'VALUE'
48
+ # - :_unlike_ # - column NOT LIKE 'VALUE'
49
+ #
50
+ # @note if type not given,
51
+ # Rear will use the type of the column with same name, if any.
52
+ # if no column found, it will use :text
53
+ #
54
+ # @note :radio, :checkbox and :select filters requires a block to run.
55
+ # block should return an Array or a Hash.
56
+ # use an Array when stored keys are the same as displayed values.
57
+ # use a Hash when stored keys are different.
58
+ # Important! if no block given, Rear will search for a column
59
+ # with same name and type and inherit options from there.
60
+ # so if you have say a :checkbox column named :colors with defined options,
61
+ # you only need to do `filter :colors`, without specifying type and options.
62
+ # type and options will be inherited from earlier defined column.
63
+ #
64
+ # @example
65
+ #
66
+ # class Page < ActiveRecord::Base
67
+ # # ...
68
+ # include Rear
69
+ # rear do
70
+ #
71
+ # # text filter using :like comparison function
72
+ # filter :name
73
+ #
74
+ # # text filter using :eql comparison function
75
+ # filter :name, :cmp => :eql
76
+ #
77
+ # # date filter using :eql comparison function
78
+ # filter :created_at, :date
79
+ #
80
+ # # date filter using :gte comparison function
81
+ # filter :created_at, :date, :cmp => :gte
82
+ #
83
+ # # dropdown filter using :eql comparison function
84
+ # filter :color, :select do
85
+ # ['Red', 'Green', 'Blue']
86
+ # end
87
+ #
88
+ # # dropdown filter using :like comparison function
89
+ # filter :color, :select, :cmp => :like do
90
+ # ['Red', 'Green', 'Blue']
91
+ # end
92
+ # end
93
+ # end
94
+ #
95
+ # @example :radio filter using Hash
96
+ #
97
+ # rear do
98
+ # filter :color, :radio do
99
+ # {'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'}
100
+ # end
101
+ # end
102
+ #
103
+ # @example inheriting type and options from a earlier defined column
104
+ #
105
+ # rear do
106
+ # column :colors, :checkbox do
107
+ # options 'Red', 'Green', 'Blue'
108
+ # end
109
+ #
110
+ # filter :colors # type and options inherited from :colors column
111
+ # end
112
+ #
113
+ # @param [Symbol] column
114
+ # @param [Symbol] type
115
+ # @param [Hash] opts_and_or_html_attrs
116
+ # @options opts_and_or_html_attrs :cmp comparison function
117
+ # @options opts_and_or_html_attrs :label
118
+ # @param [Proc] options block used on :select, :radio and :checkbox filters
119
+ # should return Array or Hash.
120
+ #
121
+ def filter column, type = nil, opts_and_or_html_attrs = {}, &proc
122
+
123
+ opts = (opts_and_or_html_attrs||{}).dup
124
+ type.is_a?(Hash) && (opts = type.dup) && (type = nil) && (opts_and_or_html_attrs = nil)
125
+ matching_column = columns.find {|c| c && c.first == column}
126
+
127
+ # if no type given, inheriting it from a column with same name, if any.
128
+ type ||= (matching_column||[])[1]
129
+ type = FILTERS__DEFAULT_TYPE unless FILTERS__HANDLED_TYPES.include?(type)
130
+
131
+ # if filter is of :select, :radio or :checkbox type and no options block given,
132
+ # inheriting it from a column with same name, if any.
133
+ if proc.nil? && matching_column && matching_column[1] == type
134
+ mci = RearInput.new(matching_column[0], type, &matching_column[3])
135
+ mci.optioned? && proc = lambda { mci.options }
136
+ end
137
+
138
+ # using defaults if no comparison function given
139
+ unless cmp = opts.delete(:cmp)
140
+ cmp = case type
141
+ when :text, :string
142
+ :like
143
+ when :checkbox
144
+ :in
145
+ else
146
+ :eql
147
+ end
148
+ end
149
+
150
+ unless label = opts.delete(:label)
151
+ label = column.to_s
152
+ label << '?' if type == :boolean
153
+ end
154
+
155
+ (filters[column.to_sym] ||= {})[cmp] = {
156
+ template: 'filters/%s.slim' % type,
157
+ type: type,
158
+ label: label.freeze,
159
+ decorative?: opts.delete(:decorative?),
160
+ attrs: opts.freeze,
161
+ proc: proc
162
+ }.freeze
163
+ end
164
+
165
+ # sometimes you need to filter by some value that has too much options.
166
+ # For ex. you want to filter pages by author and there are about 1000 authors in db.
167
+ # displaying all authors within a single dropdown filter is kinda cumbersome.
168
+ # we need to somehow narrow them down.
169
+ # decorative filters allow to do this with easy.
170
+ # in our case, we do not display the authors until a letter selected.
171
+ #
172
+ # @example
173
+ # class Pages < E
174
+ # include Rear
175
+ # model PageModel
176
+ #
177
+ # decorative_filter :letter, :select do
178
+ # ('A'..'Z').to_a
179
+ # end
180
+ #
181
+ # filter :author_id, :select do
182
+ # if letter = filter?(:letter) # use here the name of decorative filter
183
+ # authors = {}
184
+ # AuthorModel.all(:name.like => "%#{letter}%").each |a|
185
+ # authors[a.id] = a.name
186
+ # end
187
+ # authors
188
+ # else
189
+ # {"" => "Select a letter please"}
190
+ # end
191
+ # end
192
+ # end
193
+ #
194
+ # @note
195
+ # decorative filters will not actually query the db, so you can name them as you want.
196
+ #
197
+ # @note
198
+ # decorative filters does not support custom comparison functions
199
+ #
200
+ def decorative_filter *args, &proc
201
+ html_attrs = args.last.is_a?(Hash) ? Hash[args.pop] : {}
202
+ setup = {decorative?: true, cmp: FILTERS__DECORATIVE_CMP}
203
+ filter *args << html_attrs.merge(setup), &proc
204
+ end
205
+
206
+ # @example Array with default comparison function
207
+ # quick_filter :color, 'Red', 'Green', 'Blue'
208
+ # ... WHERE color = '[Red|Green|Blue]'
209
+ #
210
+ # @example Array with custom comparison function
211
+ # quick_filter :color, 'Red', 'Green', 'Blue', :cmp => :like
212
+ # ... WHERE color LIKE '[Red|Green|Blue]'
213
+ #
214
+ # @example Hash with default comparison function
215
+ # quick_filter :color, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
216
+ # ... WHERE color = '[r|g|b]'
217
+ #
218
+ # @example Hash with custom comparison function
219
+ # quick_filter :color, :cmp => :like, 'r' => 'Red', 'g' => 'Green', 'b' => 'Blue'
220
+ # ... WHERE color LIKE '%[r|g|b]%'
221
+ #
222
+ # @example Hash with comparison function defined per filter
223
+ # quick_filter :color, [:like, 'r'] => 'Red', 'g' => 'Green', 'b' => 'Blue'
224
+ # on Red
225
+ # ... WHERE color LIKE '%r%'
226
+ # on Green or Blue
227
+ # ... WHERE color = '[g|b]'
228
+ #
229
+ def quick_filter column, *args
230
+
231
+ options = args.last.is_a?(Hash) ? args.pop : {}
232
+ cmp = options.delete(:cmp) || :eql
233
+ query_formats = FILTERS__QUERY_MAP.call(orm)
234
+ if query_format = query_formats[cmp]
235
+ options = Hash[options.map do |k,v|
236
+ [
237
+ v.to_s,
238
+ k.is_a?(Array) ? [query_formats[k.first], k.last] : [query_format, k]
239
+ ]
240
+ end]
241
+
242
+ # if options provided as arguments, adding them to options Hash
243
+ args.each {|a| options[a.to_s] = [query_format, a.to_s] }
244
+
245
+ # if no options given,
246
+ # inheriting them from a column with same name, if any.
247
+ if options.empty? && mc = columns.find {|c| c && c.first == column}
248
+ mci = RearInput.new(mc[0], mc[1], &mc[3])
249
+ mci.optioned? && mci.options.each_pair do |k,v|
250
+ options[v.to_s] = [query_format, k]
251
+ end
252
+ end
253
+
254
+ quick_filters[column.to_sym] = options
255
+ end
256
+ end
257
+
258
+
259
+ # Used when you need fine-tuned control over displayed items.
260
+ # Internal filters wont render any inputs, they will work under the hood.
261
+ # `internal_filter` requires a block that should return a list of matching items.
262
+
263
+ # @example Display only articles newer than 2010
264
+
265
+ # class Article
266
+ # include DataMapper::Resource
267
+
268
+ # property :id, Serial
269
+ # # ...
270
+ # property :created_at, Date, index: true
271
+ # end
272
+
273
+ # Rear.register Article do
274
+ # # ...
275
+
276
+ # internal_filter do
277
+ # Article.all(:created_at.gt => Date.new(2010))
278
+ # end
279
+ # end
280
+
281
+ # @example Filter articles by category
282
+
283
+ # class Article < ActiveRecord::Base
284
+ # belongs_to :category
285
+ # end
286
+
287
+ # Rear.register Article do
288
+
289
+ # # firstly lets render a decorative filter
290
+ # # that will render a list of categories to choose from
291
+ # decorative_filter :Category do
292
+ # Hash[ Category.all.map {|c| [c.id, c.name]} ]
293
+ # end
294
+
295
+ # # then we using internal_filter
296
+ # # to yield selected category and filter articles
297
+ # internal_filter do
298
+ # if category_id = filter?(:Category)
299
+ # Article.all(category_id: category_id.to_i)
300
+ # end
301
+ # end
302
+ # end
303
+ #
304
+ def internal_filter &proc
305
+ # instance_exec at runtime is expensive enough,
306
+ # so compiling procs into methods at load time.
307
+ chunks = [self.to_s, proc.__id__]
308
+ name = ('__rear__%s__' % chunks.join('_').gsub(/\W/, '_')).to_sym
309
+ define_method name, &proc
310
+ private name
311
+ internal_filters.push(name)
312
+ end
313
+
314
+ end