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.
- 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
|