rear 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +20 -0
- data/LICENSE +19 -0
- data/README.md +101 -0
- data/Rakefile +79 -0
- data/assets/api.js +307 -0
- data/assets/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css +8 -0
- data/assets/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js +26 -0
- data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
- data/assets/bootstrap/css/bootstrap.min.css +9 -0
- data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
- data/assets/bootstrap/js/bootstrap.min.js +6 -0
- data/assets/jquery.js +5 -0
- data/assets/noty/jquery.noty.js +520 -0
- data/assets/noty/layouts/top.js +34 -0
- data/assets/noty/layouts/topRight.js +43 -0
- data/assets/noty/promise.js +432 -0
- data/assets/noty/themes/default.js +156 -0
- data/assets/select2-bootstrap.css +86 -0
- data/assets/select2/select2-spinner.gif +0 -0
- data/assets/select2/select2.css +652 -0
- data/assets/select2/select2.min.js +22 -0
- data/assets/select2/select2.png +0 -0
- data/assets/select2/select2x2.png +0 -0
- data/assets/ui.css +75 -0
- data/assets/xhr.js +4 -0
- data/bin/rear +65 -0
- data/docs/Assocs.md +100 -0
- data/docs/Columns.md +404 -0
- data/docs/Deploy.md +62 -0
- data/docs/FileManager.md +75 -0
- data/docs/Filters.md +341 -0
- data/docs/Setup.md +201 -0
- data/lib/rear.rb +13 -0
- data/lib/rear/actions.rb +98 -0
- data/lib/rear/constants.rb +61 -0
- data/lib/rear/controller_setup.rb +249 -0
- data/lib/rear/helpers.rb +17 -0
- data/lib/rear/helpers/class.rb +46 -0
- data/lib/rear/helpers/columns.rb +68 -0
- data/lib/rear/helpers/filters.rb +147 -0
- data/lib/rear/helpers/generic.rb +73 -0
- data/lib/rear/helpers/order.rb +47 -0
- data/lib/rear/helpers/pager.rb +35 -0
- data/lib/rear/helpers/render.rb +37 -0
- data/lib/rear/home_controller.rb +10 -0
- data/lib/rear/input.rb +341 -0
- data/lib/rear/orm.rb +73 -0
- data/lib/rear/rear.rb +74 -0
- data/lib/rear/setup.rb +9 -0
- data/lib/rear/setup/associations.rb +33 -0
- data/lib/rear/setup/columns.rb +109 -0
- data/lib/rear/setup/filters.rb +314 -0
- data/lib/rear/setup/generic.rb +59 -0
- data/lib/rear/setup/menu.rb +39 -0
- data/lib/rear/templates/editor/ace.slim +7 -0
- data/lib/rear/templates/editor/assocs.slim +10 -0
- data/lib/rear/templates/editor/boolean.slim +5 -0
- data/lib/rear/templates/editor/bulk_edit.slim +75 -0
- data/lib/rear/templates/editor/checkbox.slim +5 -0
- data/lib/rear/templates/editor/ckeditor.slim +7 -0
- data/lib/rear/templates/editor/date.slim +9 -0
- data/lib/rear/templates/editor/datetime.slim +9 -0
- data/lib/rear/templates/editor/layout.slim +103 -0
- data/lib/rear/templates/editor/password.slim +1 -0
- data/lib/rear/templates/editor/radio.slim +5 -0
- data/lib/rear/templates/editor/select.slim +3 -0
- data/lib/rear/templates/editor/string.slim +1 -0
- data/lib/rear/templates/editor/text.slim +1 -0
- data/lib/rear/templates/editor/time.slim +9 -0
- data/lib/rear/templates/error.slim +36 -0
- data/lib/rear/templates/filters/boolean.slim +10 -0
- data/lib/rear/templates/filters/checkbox.slim +15 -0
- data/lib/rear/templates/filters/date.slim +10 -0
- data/lib/rear/templates/filters/datetime.slim +10 -0
- data/lib/rear/templates/filters/layout.slim +26 -0
- data/lib/rear/templates/filters/radio.slim +14 -0
- data/lib/rear/templates/filters/select.slim +9 -0
- data/lib/rear/templates/filters/string.slim +3 -0
- data/lib/rear/templates/filters/text.slim +3 -0
- data/lib/rear/templates/filters/time.slim +10 -0
- data/lib/rear/templates/home.slim +0 -0
- data/lib/rear/templates/layout.slim +78 -0
- data/lib/rear/templates/pager.slim +22 -0
- data/lib/rear/templates/pane/ace.slim +2 -0
- data/lib/rear/templates/pane/assocs.slim +62 -0
- data/lib/rear/templates/pane/boolean.slim +2 -0
- data/lib/rear/templates/pane/checkbox.slim +5 -0
- data/lib/rear/templates/pane/ckeditor.slim +2 -0
- data/lib/rear/templates/pane/date.slim +2 -0
- data/lib/rear/templates/pane/datetime.slim +2 -0
- data/lib/rear/templates/pane/layout.slim +111 -0
- data/lib/rear/templates/pane/password.slim +2 -0
- data/lib/rear/templates/pane/quickview.slim +21 -0
- data/lib/rear/templates/pane/radio.slim +5 -0
- data/lib/rear/templates/pane/select.slim +5 -0
- data/lib/rear/templates/pane/string.slim +2 -0
- data/lib/rear/templates/pane/text.slim +2 -0
- data/lib/rear/templates/pane/time.slim +2 -0
- data/lib/rear/utils.rb +288 -0
- data/rear.gemspec +27 -0
- data/test/helpers.rb +33 -0
- data/test/models/ar.rb +52 -0
- data/test/models/dm.rb +53 -0
- data/test/models/sq.rb +58 -0
- data/test/setup.rb +4 -0
- data/test/templates/adhoc/book/editor/name.slim +1 -0
- data/test/templates/adhoc/book/editor/string.slim +1 -0
- data/test/templates/adhoc/book/pane/name.slim +1 -0
- data/test/templates/adhoc/book/pane/string.slim +1 -0
- data/test/templates/shared/shared-templates/editor/string.slim +1 -0
- data/test/templates/shared/shared-templates/pane/string.slim +1 -0
- data/test/test__assocs.rb +249 -0
- data/test/test__columns.rb +269 -0
- data/test/test__crud.rb +81 -0
- data/test/test__custom_templates.rb +147 -0
- data/test/test__filters.rb +228 -0
- metadata +220 -0
data/docs/Setup.md
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
|
2
|
+
## Primary key
|
3
|
+
|
4
|
+
**Rear** will do its best to correctly detect the primary key of managed model.
|
5
|
+
|
6
|
+
In case it is failing to do so, you can set primary key manually by using `primary_key` method:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
primary_key :ItemID
|
10
|
+
```
|
11
|
+
|
12
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
13
|
+
|
14
|
+
## Ordering
|
15
|
+
|
16
|
+
**Rear** will fetch items in the order specified by your model or db.
|
17
|
+
|
18
|
+
It is also allow to set specific order by using `order_by` method:
|
19
|
+
|
20
|
+
`DataMapper`:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
order_by :name, :id.desc
|
24
|
+
```
|
25
|
+
|
26
|
+
`ActiveRecord`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
order_by 'name, id DESC'
|
30
|
+
```
|
31
|
+
|
32
|
+
Also, on pane pages it is possible to order items by a specific column by clicking on it.
|
33
|
+
By default it will sort items in ascending order. To use descending order click on column one more time.
|
34
|
+
|
35
|
+
When sorting items this way, "ORDER BY" statement will use only selected column,
|
36
|
+
e.g. "ORDER BY name" or "ORDER BY date" etc.
|
37
|
+
|
38
|
+
To make Rear to sort by multiple columns when clicking on a specific one, set custom `order_by` for that column:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class News
|
42
|
+
include DataMapper::Resource
|
43
|
+
|
44
|
+
property :id, Serial
|
45
|
+
property :name, String
|
46
|
+
property :date, Date
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
Rear.register News do
|
51
|
+
|
52
|
+
# making Rear to order by both name and date when name clicked
|
53
|
+
input :name do
|
54
|
+
order_by :name, :date
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
**Important!** do not pass ordering vector when setting costom `order_by` for columns. Vector will be added automatically, so pass only column names.
|
60
|
+
|
61
|
+
**Example:** this will break ordering cause name specifies desc vector
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
input :name do
|
65
|
+
order_by :name.desc, :date
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
72
|
+
|
73
|
+
|
74
|
+
## Items per page
|
75
|
+
|
76
|
+
By default **Rear** will display 10 items per page.
|
77
|
+
|
78
|
+
To have it displaying more or less, use `items_per_page` method or its alias - `ipp`:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
items_per_page 50
|
82
|
+
```
|
83
|
+
|
84
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
85
|
+
|
86
|
+
|
87
|
+
## Menu Label
|
88
|
+
|
89
|
+
**Rear** will build a menu that will include all managed models.
|
90
|
+
|
91
|
+
By default, model name will be used as label:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
class PageModel < ActiveRecord::Base
|
95
|
+
# ...
|
96
|
+
end
|
97
|
+
|
98
|
+
Rear.register PageModel
|
99
|
+
```
|
100
|
+
|
101
|
+
This will display "PageModel" in menu.
|
102
|
+
|
103
|
+
To have a custom label displayed, use `menu_label` method:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
Rear.register PageModel do
|
107
|
+
menu_label :Pages
|
108
|
+
# ...
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
Now it will display "Pages" in menu.
|
113
|
+
|
114
|
+
It is also possible to use `label` alias to set menu label.
|
115
|
+
|
116
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
117
|
+
|
118
|
+
|
119
|
+
## Menu Positioning
|
120
|
+
|
121
|
+
**Rear's** menu will display managed models in the the order they was defined.
|
122
|
+
|
123
|
+
To have a model displayed upper, set its position higher.
|
124
|
+
|
125
|
+
Or decrease position to have a model displayed lower.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
class City < ActiveRecord::Base
|
129
|
+
# ...
|
130
|
+
end
|
131
|
+
|
132
|
+
class Country < ActiveRecord::Base
|
133
|
+
# ...
|
134
|
+
end
|
135
|
+
|
136
|
+
class State < ActiveRecord::Base
|
137
|
+
# ...
|
138
|
+
end
|
139
|
+
|
140
|
+
Rear.register City, Country, State
|
141
|
+
```
|
142
|
+
|
143
|
+
This will build a menu like `City | Country | State`.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
Rear.register City, Country, State do |model|
|
147
|
+
menu_position({
|
148
|
+
Country => 1000,
|
149
|
+
State => 500,
|
150
|
+
City => 100,
|
151
|
+
}[model])
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
This will build a menu like `Country | State | City`.
|
156
|
+
|
157
|
+
It is also possible to use `position` alias to set menu position.
|
158
|
+
|
159
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
160
|
+
|
161
|
+
|
162
|
+
## Menu Grouping
|
163
|
+
|
164
|
+
By default **Rear** will build a "flat" menu.
|
165
|
+
|
166
|
+
To have some models displayed under some group, use `menu_group` method:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
Rear.register City, Country, State do
|
170
|
+
menu_group :Location
|
171
|
+
# or
|
172
|
+
under :Location # `under` is an alias for `menu_group`
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
This will create "Location" group that will display "City", "Country" and "State" links on hover.
|
177
|
+
|
178
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
179
|
+
|
180
|
+
|
181
|
+
## ReadOnly Mode
|
182
|
+
|
183
|
+
When you need to display a whole model in readonly mode, use `readonly!` method:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class State < ActiveRecord::Base
|
187
|
+
# ...
|
188
|
+
end
|
189
|
+
|
190
|
+
Rear.register State do
|
191
|
+
readonly!
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
In readonly mode items can not be created/updated nor deleted.
|
196
|
+
|
197
|
+
Also all associations will be in readonly mode.
|
198
|
+
|
199
|
+
|
200
|
+
**[ [contents ↑](https://github.com/espresso/rear#tutorial) ]**
|
201
|
+
|
data/lib/rear.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'el'
|
2
|
+
require 'slim'
|
3
|
+
|
4
|
+
require 'rear/constants'
|
5
|
+
require 'rear/utils'
|
6
|
+
require 'rear/orm'
|
7
|
+
require 'rear/actions'
|
8
|
+
require 'rear/input'
|
9
|
+
require 'rear/rear'
|
10
|
+
require 'rear/setup'
|
11
|
+
require 'rear/helpers'
|
12
|
+
require 'rear/controller_setup'
|
13
|
+
require 'rear/home_controller'
|
data/lib/rear/actions.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
module RearActions
|
2
|
+
include RearConstants
|
3
|
+
|
4
|
+
def get_index
|
5
|
+
reander_l(:layout) { reander_p 'pane/layout' }
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_quickview
|
9
|
+
reander_p 'pane/quickview'
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_edit id
|
13
|
+
reander_l(:layout) { reander_p 'editor/layout' }
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_bulk_edit
|
17
|
+
@items = params[:items].to_s.gsub(/[^\d|\s]/, '')
|
18
|
+
self.item, self.item_id = model.new, 0
|
19
|
+
reander_p 'editor/bulk_edit'
|
20
|
+
end
|
21
|
+
|
22
|
+
def post_bulk_edit
|
23
|
+
data = Hash[params]
|
24
|
+
(columns = data.delete('rear-bulk_editor-crudifier_toggler')).is_a?(Array) ||
|
25
|
+
halt(400, 'Nothing to update. Please edit at least one column.')
|
26
|
+
items = data.delete('rear-bulk_editor-items').to_s.strip.split
|
27
|
+
items.empty? && halt(400, 'No items selected')
|
28
|
+
|
29
|
+
data.reject! {|k,v| !columns.include?(k)}
|
30
|
+
updated, failed = [], []
|
31
|
+
items.each do |id|
|
32
|
+
status, h, body = invoke_via_put(:crud, id, data)
|
33
|
+
status == 200 ? updated << id : failed << ['%s Failed' % id, *body]
|
34
|
+
end
|
35
|
+
failed.any? && styled_halt(500, failed)
|
36
|
+
"Successfully Updated Items:\n%s" % updated.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
|
40
|
+
reander_p 'pane/assocs'
|
41
|
+
end
|
42
|
+
|
43
|
+
def post_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
|
44
|
+
case @reverse_assoc.type
|
45
|
+
when :belongs_to, :has_one
|
46
|
+
@reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, @reverse_assoc.target_item
|
47
|
+
when :has_many
|
48
|
+
@reverse_assoc.source_item.send(@reverse_assoc.name).each do |ti|
|
49
|
+
ti[pkey] == @reverse_assoc.target_item[pkey] && halt(400, "Relation already exists")
|
50
|
+
end
|
51
|
+
if sequel?
|
52
|
+
m = 'add_' << orm.singularize(@reverse_assoc.name)
|
53
|
+
@reverse_assoc.source_item.send m, @reverse_assoc.target_item
|
54
|
+
else
|
55
|
+
@reverse_assoc.source_item.send(@reverse_assoc.name) << @reverse_assoc.target_item
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@reverse_assoc.source_item.save
|
59
|
+
end
|
60
|
+
|
61
|
+
def delete_reverse_assoc source_ctrl, assoc_type, assoc_name, item_id, attached = nil
|
62
|
+
case @reverse_assoc.type
|
63
|
+
when :belongs_to, :has_one
|
64
|
+
@reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, nil
|
65
|
+
when :has_many
|
66
|
+
if sequel?
|
67
|
+
(@reverse_assoc.source_item.send(@reverse_assoc.name)||[]).each do |ti|
|
68
|
+
next unless ti[pkey] == @reverse_assoc.target_item[pkey]
|
69
|
+
m = 'remove_' << orm.singularize(@reverse_assoc.name)
|
70
|
+
@reverse_assoc.source_item.send m, ti
|
71
|
+
end
|
72
|
+
else
|
73
|
+
target_items = Array.new(@reverse_assoc.source_item.send(@reverse_assoc.name)||[]).reject do |ti|
|
74
|
+
ti[pkey] == @reverse_assoc.target_item[pkey]
|
75
|
+
end
|
76
|
+
@reverse_assoc.source_item.send '%s=' % @reverse_assoc.name, target_items
|
77
|
+
end
|
78
|
+
end
|
79
|
+
@reverse_assoc.source_item.save
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_delete_selected
|
83
|
+
halt(400, '%s is in readonly mode' % model) if readonly?
|
84
|
+
if (ids = params[:items].to_s.split.map(&:to_i).select {|r| r > 0}).any?
|
85
|
+
orm.delete_multiple(*ids)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def html_filters partial = false
|
90
|
+
partial ? render_filters : reander_p('filters/layout')
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_assets(*)
|
94
|
+
env['PATH_INFO'] = env['PATH_INFO'].to_s.sub(ASSETS__SUFFIX_REGEXP, '')
|
95
|
+
send_files @__rear__assets_fullpath ? @__rear__assets_fullpath :
|
96
|
+
(@__rear__assets_path ? File.join(app.root, @__rear__assets_path) : ASSETS__PATH)
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RearConstants
|
2
|
+
|
3
|
+
PATH__TEMPLATES = (File.expand_path('../templates', __FILE__) + '/').freeze
|
4
|
+
|
5
|
+
ASSETS__PATH = (File.expand_path('../../../assets', __FILE__) + '/').freeze
|
6
|
+
ASSETS__SUFFIX = '-rear'.freeze
|
7
|
+
ASSETS__SUFFIX_REGEXP = /#{Regexp.escape ASSETS__SUFFIX}\Z/.freeze
|
8
|
+
|
9
|
+
COLUMNS__HANDLED_TYPES = [
|
10
|
+
:string, :text,
|
11
|
+
:date, :time, :datetime,
|
12
|
+
:radio, :checkbox, :select,
|
13
|
+
:boolean
|
14
|
+
].freeze
|
15
|
+
COLUMNS__DEFAULT_TYPE = :string
|
16
|
+
COLUMNS__BOOLEAN_MAP = {true => 'Y', false => 'N'}.freeze
|
17
|
+
COLUMNS__PANE_MAX_LENGTH = 255
|
18
|
+
|
19
|
+
PAGER__SIDE_PAGES = 5
|
20
|
+
|
21
|
+
FILTERS__HANDLED_TYPES = COLUMNS__HANDLED_TYPES
|
22
|
+
FILTERS__DEFAULT_TYPE = :string
|
23
|
+
FILTERS__DECORATIVE_CMP = :decorative
|
24
|
+
FILTERS__STR_TO_BOOLEAN = {"true" => true, "false" => false}.freeze
|
25
|
+
FILTERS__QUERY_MAP = lambda do |orm|
|
26
|
+
# using lambda will always return a new copy of Hash, so no need to deep copy it
|
27
|
+
default_query_map = {
|
28
|
+
gt: ['%s > ?', '%s'],
|
29
|
+
lt: ['%s < ?', '%s'],
|
30
|
+
gte: ['%s >= ?', '%s'],
|
31
|
+
lte: ['%s <= ?', '%s'],
|
32
|
+
not: ['%s <> ?', '%s'],
|
33
|
+
eql: ['%s = ?', '%s'],
|
34
|
+
|
35
|
+
# use left and right wildcards - '%VALUE%'
|
36
|
+
like: ['%s LIKE ?', '%%%s%'],
|
37
|
+
unlike: ['%s NOT LIKE ?', '%%%s%'],
|
38
|
+
|
39
|
+
# use only left wildcard - exact match for end of line - LIKE '%VALUE'
|
40
|
+
_like: ['%s LIKE ?', '%%%s'],
|
41
|
+
_unlike: ['%s NOT LIKE ?', '%%%s'],
|
42
|
+
|
43
|
+
# use only right wildcard - exact match for beginning of line - LIKE 'VALUE%'
|
44
|
+
like_: ['%s LIKE ?', '%s%'],
|
45
|
+
unlike_: ['%s NOT LIKE ?', '%s%'],
|
46
|
+
|
47
|
+
in: ['%s IN ?'],
|
48
|
+
csl: ['%s IN ?'], # comma separated list
|
49
|
+
FILTERS__DECORATIVE_CMP => []
|
50
|
+
}
|
51
|
+
{
|
52
|
+
ar: default_query_map.merge(:in => ['%s IN (?)'], :csl => ['%s IN (?)']),
|
53
|
+
dm: default_query_map,
|
54
|
+
sq: default_query_map,
|
55
|
+
}[orm]
|
56
|
+
end
|
57
|
+
|
58
|
+
ASSOCS__STRUCT = lambda do
|
59
|
+
{belongs_to: {}, has_one: {}, has_many: {}}
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
class RearControllerSetup
|
2
|
+
include RearConstants
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def init(*args, &proc)
|
7
|
+
new.init(*args, &proc)
|
8
|
+
end
|
9
|
+
|
10
|
+
def crudify(*args, &proc)
|
11
|
+
new.crudify(*args, &proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def init ctrl
|
17
|
+
(@ctrl = ctrl).class_exec do
|
18
|
+
extend RearSetup
|
19
|
+
extend RearHelpers::ClassMixin
|
20
|
+
include RearConstants
|
21
|
+
include RearHelpers::InstanceMixin
|
22
|
+
import RearActions
|
23
|
+
|
24
|
+
include EL::Ace if defined?(EL::Ace)
|
25
|
+
include EL::CKE if defined?(EL::CKE)
|
26
|
+
|
27
|
+
reject_automount!
|
28
|
+
EUtils.register_extra_engines!
|
29
|
+
end
|
30
|
+
|
31
|
+
define_setup_methods
|
32
|
+
define_error_handlers
|
33
|
+
end
|
34
|
+
|
35
|
+
def crudify ctrl, model, opts, &proc
|
36
|
+
(@ctrl = ctrl).class_exec do
|
37
|
+
@__rear__orm = RearUtils.orm(model)
|
38
|
+
@__rear__assocs = RearUtils.extract_assocs(model)
|
39
|
+
@__rear__real_columns, @__rear__pkey = RearUtils.extract_columns(model)
|
40
|
+
@__rear__real_columns.each do |c|
|
41
|
+
input(*c) unless @__rear__assocs[:belongs_to].any? {|a,s|
|
42
|
+
s[:belongs_to_keys][:source] == c.first
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
setup_crudifier(opts, &proc)
|
48
|
+
define_association_hooks
|
49
|
+
define_pane_hooks
|
50
|
+
define_editor_hooks
|
51
|
+
define_default_filters
|
52
|
+
end
|
53
|
+
|
54
|
+
def setup_crudifier opts, &proc
|
55
|
+
@ctrl.class_exec do
|
56
|
+
|
57
|
+
# use :destroy! on DataMapper
|
58
|
+
opts[:delete] ||= :destroy! if orm == :dm
|
59
|
+
|
60
|
+
# asking Espresso to define CRUD actions
|
61
|
+
crudify(model, :crud, opts, &proc)
|
62
|
+
|
63
|
+
alias_before :post_crud, :create, :save
|
64
|
+
alias_before :put_crud, :update, :save
|
65
|
+
alias_before :delete_crud, :destroy
|
66
|
+
|
67
|
+
before :create, :update, :destroy do
|
68
|
+
halt(400, '%s is in readonly mode' % model) if readonly?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def define_default_filters
|
74
|
+
@ctrl.class_exec do
|
75
|
+
# built-in filter for all controllers. it will search by primary key
|
76
|
+
filter(pkey, cmp: :csl, class: 'input-small search-query')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def define_setup_methods
|
81
|
+
@ctrl.class_exec do
|
82
|
+
|
83
|
+
# allow to set path to custom templates.
|
84
|
+
# to use custom templates install them via "$ rear i:t PATH"
|
85
|
+
# and use `rear_templates PATH` when mounting Rear controllers.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# # install templates in views/ folder
|
89
|
+
# $ rear i:t views/
|
90
|
+
#
|
91
|
+
# # set path to custom templates when mounting Rear controllers
|
92
|
+
# E.new do
|
93
|
+
# mount Rear.controllers, '/admin' do
|
94
|
+
# rear_templates 'views/rear/'
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
def rear_templates path
|
99
|
+
return @__rear__templates_fullpath = path.to_s if path =~ /\A(\w\:)?\// && @__rear__templates_fullpath.nil?
|
100
|
+
@__rear__templates_path = path.to_s if @__rear__templates_path.nil?
|
101
|
+
end
|
102
|
+
define_setup_method :rear_templates
|
103
|
+
|
104
|
+
# similar to `rear_templates` except it sets path to custom assets
|
105
|
+
def rear_assets path
|
106
|
+
return @__rear__assets_fullpath = path.to_s if path =~ /\A(\w\:)?\// && @__rear__assets_fullpath.nil?
|
107
|
+
@__rear__assets_path = path.to_s if @__rear__assets_path.nil?
|
108
|
+
end
|
109
|
+
define_setup_method :rear_assets
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def define_error_handlers
|
115
|
+
@ctrl.class_exec do
|
116
|
+
error 500 do |e|
|
117
|
+
error = if e.respond_to?(:backtrace)
|
118
|
+
e.backtrace.inject([e.message]) {|bt,l| bt << l}
|
119
|
+
elsif e.is_a?(Array)
|
120
|
+
e
|
121
|
+
else
|
122
|
+
[e.to_s]
|
123
|
+
end
|
124
|
+
out = rq.xhr? ? error*"\n" : reander_l(:layout) {reander_p :error, error: error}
|
125
|
+
halt 500, out
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def define_association_hooks
|
131
|
+
@ctrl.class_exec do
|
132
|
+
# all communications between associated models happens through primary keys.
|
133
|
+
# it does not matter what keys models using to associate one to each other,
|
134
|
+
# Rear will always use pkeys to display/update associated objects.
|
135
|
+
# this is achieved by using ORM setters for associated objects,
|
136
|
+
# like `state=` on `belongs_to :state` assocs
|
137
|
+
# or `cities<<` on `has_many :cities` etc.
|
138
|
+
# so it will basically fetch remote objects by pkey and feed them to that setters.
|
139
|
+
#
|
140
|
+
# hooks order is important. this one should always go first
|
141
|
+
before /reverse_assoc/ do
|
142
|
+
ctrl = action_params[:source_ctrl].split('::').inject(Object) {|f,c| f.const_get(c)}
|
143
|
+
assoc = ctrl.assocs[action_params[:assoc_type].to_sym][action_params[:assoc_name].to_sym]
|
144
|
+
readonly = ctrl.readonly_assocs.include?(assoc[:name]) || assoc[:readonly] || ctrl.readonly?
|
145
|
+
halt(400, '%s is in readonly mode' % model) if readonly && (post? || delete?)
|
146
|
+
struct = {
|
147
|
+
type: assoc[:type],
|
148
|
+
name: assoc[:name],
|
149
|
+
dom_id: assoc[:dom_id] + (action_params[:attached] ? '' : '_detached'),
|
150
|
+
readonly: readonly ? true : false, # using true/false here cause it will be converted to string and fed to javascript
|
151
|
+
attached: action_params[:attached],
|
152
|
+
route: route(action, *action_params__array[0..3]),
|
153
|
+
source_item: RearORM.new(ctrl.model, ctrl.pkey)[action_params[:item_id].to_i],
|
154
|
+
target_item: orm[params[:target_item_id].to_i]||{},
|
155
|
+
source_key: nil,
|
156
|
+
target_key: nil,
|
157
|
+
}
|
158
|
+
if struct[:type] == :belongs_to && (struct[:attached] || struct[:source_item].nil?)
|
159
|
+
struct[:source_key], struct[:target_key] =
|
160
|
+
assoc[:belongs_to_keys].values_at(:source, :target)
|
161
|
+
end
|
162
|
+
@reverse_assoc = Struct.new(*struct.keys.map(&:to_sym)).new(*struct.values).freeze
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def define_pane_hooks
|
168
|
+
@ctrl.class_exec do
|
169
|
+
before :get_index, :get_reverse_assoc, :get_quickview do
|
170
|
+
|
171
|
+
conditions = filters_to_sql
|
172
|
+
|
173
|
+
source_item, source_assoc = nil
|
174
|
+
if @reverse_assoc && @reverse_assoc.attached
|
175
|
+
if source_item = @reverse_assoc.source_item
|
176
|
+
source_assoc = @reverse_assoc.name
|
177
|
+
else
|
178
|
+
conditions = {conditions: {pkey => 0}}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
total_items = source_item ?
|
183
|
+
orm.assoc_count(source_assoc, source_item, conditions) :
|
184
|
+
orm.count(conditions)
|
185
|
+
|
186
|
+
total_pages = (total_items.to_f / __rear__.ipp.to_f).ceil
|
187
|
+
side_pages = xhr? ? 2 : PAGER__SIDE_PAGES
|
188
|
+
|
189
|
+
current_page = params[:page].to_i
|
190
|
+
current_page = 1 if current_page < 1
|
191
|
+
current_page = total_pages if current_page > total_pages
|
192
|
+
|
193
|
+
page_min = current_page - side_pages
|
194
|
+
page_min = total_pages - (side_pages * 2) if (current_page + side_pages) > total_pages
|
195
|
+
page_min = 1 if page_min < 1
|
196
|
+
|
197
|
+
page_max = current_page + side_pages
|
198
|
+
page_max = side_pages * 2 if current_page < side_pages
|
199
|
+
page_max = total_pages if page_max > total_pages
|
200
|
+
|
201
|
+
offset = (current_page - 1) * __rear__.ipp
|
202
|
+
offset = 0 if offset < 0
|
203
|
+
|
204
|
+
counter = [offset + 1, offset + __rear__.ipp, total_items]
|
205
|
+
counter[0] = offset if counter[0] > total_items
|
206
|
+
counter[1] = total_items if counter[1] > total_items
|
207
|
+
|
208
|
+
@pager_context = {
|
209
|
+
total_items: total_items,
|
210
|
+
total_pages: total_pages,
|
211
|
+
current_page: current_page,
|
212
|
+
page_min: page_min,
|
213
|
+
page_max: page_max,
|
214
|
+
counter: counter.map {|n| RearUtils.number_with_delimiter(n)}
|
215
|
+
}
|
216
|
+
@pager = total_pages > 1 ? reander_p(:pager, @pager_context) : ''
|
217
|
+
|
218
|
+
conditions[:limit ] = __rear__.ipp
|
219
|
+
conditions[:offset] = offset
|
220
|
+
|
221
|
+
if order = order_params_to_sql || __rear__.order_by
|
222
|
+
conditions[:order] = order
|
223
|
+
end
|
224
|
+
|
225
|
+
@items = if source_item
|
226
|
+
orm.assoc_filter(source_assoc, source_item, conditions)
|
227
|
+
else
|
228
|
+
orm.filter(conditions)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def define_editor_hooks
|
235
|
+
@ctrl.class_exec do
|
236
|
+
before :get_edit do
|
237
|
+
if (id = action_params[:id].to_i) > 0
|
238
|
+
@item = orm[id] || halt(400, "Item with ID %s not found" % id)
|
239
|
+
@item_id = id
|
240
|
+
else
|
241
|
+
@item = @brand_new_item = model.new
|
242
|
+
@item_id = 0
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|