rear 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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/test/models/sq.rb ADDED
@@ -0,0 +1,58 @@
1
+ connection = Sequel.sqlite
2
+ Sequel::Model.db = connection
3
+
4
+ connection.create_table :books_genres do |t|
5
+ t.integer :book_id
6
+ t.integer :genre_id
7
+ end
8
+
9
+ connection.create_table :genres do |t|
10
+ primary_key :id
11
+ t.string :name
12
+ end
13
+
14
+ connection.create_table :authors do |t|
15
+ primary_key :id
16
+ t.string :name
17
+ end
18
+
19
+ connection.create_table :books do |t|
20
+ primary_key :id
21
+ t.integer :author_id
22
+ t.string :name
23
+ t.text :about
24
+ t.string :cover
25
+ t.string :colors
26
+ t.date :created_at
27
+ end
28
+
29
+ connection.create_table :states do |t|
30
+ primary_key :id
31
+ t.string :name
32
+ t.string :code
33
+ end
34
+
35
+ connection.create_table :cities do |t|
36
+ primary_key :id
37
+ t.string :name
38
+ t.string :state_code
39
+ end
40
+
41
+ class Author < Sequel::Model
42
+ end
43
+
44
+ class Genre < Sequel::Model
45
+ end
46
+
47
+ class Book < Sequel::Model
48
+ many_to_one :author
49
+ many_to_many :genres
50
+ end
51
+
52
+ class State < Sequel::Model
53
+ one_to_many :cities, key: :state_code, primary_key: :code
54
+ end
55
+
56
+ class City < Sequel::Model
57
+ many_to_one :state, key: :state_code, primary_key: :code
58
+ end
data/test/setup.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'fileutils'
2
+ require 'bundler/setup'
3
+
4
+ Bundler.require
@@ -0,0 +1 @@
1
+ input.adhoc-given-template value=value
@@ -0,0 +1 @@
1
+ input.adhoc-default-template value=value
@@ -0,0 +1 @@
1
+ p.adhoc-given-template = value
@@ -0,0 +1 @@
1
+ p.adhoc-default-template = value
@@ -0,0 +1 @@
1
+ input.custom-shared-template value=value
@@ -0,0 +1 @@
1
+ p.custom-shared-template = value
@@ -0,0 +1,249 @@
1
+ module RearTest__Assocs
2
+ class Books < E
3
+ include Rear
4
+ model Book
5
+ end
6
+
7
+ class Authors < E
8
+ include Rear
9
+ model Author
10
+ end
11
+
12
+ class Genres < E
13
+ include Rear
14
+ model Genre
15
+ end
16
+
17
+ Spec.new Authors do
18
+ Books.mount
19
+
20
+ book, book_id = new_book()
21
+ args = [
22
+ :reverse_assoc,
23
+ RearTest__Assocs::Books,
24
+ :belongs_to,
25
+ :author,
26
+ book_id
27
+ ]
28
+
29
+ Ensure 'no author attached' do
30
+ get app.route(*args, :attached)
31
+ is(last_response).ok?
32
+ are(extract_elements).empty?
33
+ end
34
+
35
+ author = Author.create(name: rand(2**64).to_s)
36
+
37
+ Should 'attach given author' do
38
+ post app.route(*args, target_item_id: author.id.to_s)
39
+ is(last_response).ok?
40
+
41
+ Ensure 'model updated' do
42
+ book.reload
43
+ expect(book.author_id) == author.id
44
+ end
45
+
46
+ Ensure 'frontend reflects updates' do
47
+ get app.route(*args, :attached)
48
+ is(last_response).ok?
49
+ expect { extract_elements.children }.any? do |c|
50
+ c.text == author.name.to_s
51
+ end
52
+ end
53
+ end
54
+
55
+ Should 'detach given author' do
56
+ delete app.route(*args)
57
+
58
+ Ensure 'model updated' do
59
+ book.reload
60
+ refute(book.author_id) == author.id
61
+ end
62
+
63
+ Ensure 'frontend reflects updates' do
64
+ get app.route(*args, :attached)
65
+ is(last_response).ok?
66
+ are(extract_elements).empty?
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ Spec.new Genres do
73
+
74
+ book, book_id = new_book()
75
+ args = [
76
+ :reverse_assoc,
77
+ RearTest__Assocs::Books,
78
+ :has_many,
79
+ :genres,
80
+ book_id
81
+ ]
82
+
83
+ Ensure 'no genres attached' do
84
+ get app.route(*args, :attached)
85
+ is(last_response).ok?
86
+ are(extract_elements).empty?
87
+ end
88
+
89
+ genre = Genre.create(name: rand(2**64).to_s)
90
+
91
+ Should 'attach given genre' do
92
+ post app.route(*args, target_item_id: genre.id.to_s)
93
+
94
+ Ensure 'model updated' do
95
+ book.reload
96
+ does(book.genres).include? genre
97
+ end
98
+
99
+ Ensure 'frontend reflects updates' do
100
+ get app.route(*args, :attached)
101
+ is(last_response).ok?
102
+ expect { extract_elements.children }.any? do |c|
103
+ c.text == genre.name.to_s
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ Should 'detach given genre' do
110
+ delete app.route(*args, target_item_id: genre.id.to_s)
111
+
112
+ Ensure 'model updated' do
113
+ book.reload
114
+ refute(book.genres).include? genre
115
+ end
116
+
117
+ Ensure 'frontend reflects updates' do
118
+ get app.route(*args, :attached)
119
+ is(last_response).ok?
120
+ are(extract_elements).empty?
121
+ end
122
+ end
123
+ end
124
+
125
+ class CustomKeys
126
+ class Child < E
127
+ include Rear
128
+ model City
129
+ end
130
+
131
+ class Parent < E
132
+ include Rear
133
+ model State
134
+ end
135
+ end
136
+
137
+ Spec.new CustomKeys::Parent do
138
+ CustomKeys::Child.mount
139
+
140
+ state = State.create(name: 'California', code: 'CA')
141
+ city = City.create
142
+ args = [
143
+ :reverse_assoc,
144
+ CustomKeys::Child,
145
+ :belongs_to,
146
+ :state,
147
+ city.id
148
+ ]
149
+
150
+ Should 'set state to CA' do
151
+ post app.route(*args, target_item_id: state.id.to_s)
152
+
153
+ Ensure 'city updated' do
154
+ city.reload
155
+ expect(city.state_code) == state.code
156
+ end
157
+
158
+ Ensure 'frontend reflects updates' do
159
+ get app.route(*args, :attached)
160
+ is(last_response).ok?
161
+ expect { extract_elements.children }.any? do |c|
162
+ c.text == state.name
163
+ end
164
+ end
165
+
166
+ end
167
+ end
168
+
169
+ Spec.new CustomKeys::Child do
170
+
171
+ state = State.create(name: 'New York', code: 'NY')
172
+ cities = ['Fulton', 'Niagara Falls'].map {|c| City.create(name: c)}
173
+ args = [
174
+ :reverse_assoc,
175
+ CustomKeys::Parent,
176
+ :has_many,
177
+ :cities,
178
+ state.id
179
+ ]
180
+
181
+ Should 'attach cities to New York' do
182
+ cities.each do |city|
183
+ post app.route(*args, target_item_id: city.id.to_s)
184
+ end
185
+
186
+ Ensure 'cities updated' do
187
+ cities.each do |city|
188
+ city.reload
189
+ is(city.state_code) == state.code
190
+ end
191
+ end
192
+
193
+ Ensure 'frontend reflects updates' do
194
+ get app.route(*args, :attached)
195
+ is(last_response).ok?
196
+ cities_html = extract_elements.children
197
+ cities.each do |city|
198
+ expect(cities_html).any? do |c|
199
+ c.text == city.name
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ end
206
+
207
+ module ReadOnly
208
+ class Books < E
209
+ include Rear
210
+ model Book
211
+ readonly_assoc :author
212
+ end
213
+ class Authors < E
214
+ include Rear
215
+ model Author
216
+ end
217
+ Spec.new Authors do
218
+ Books.mount
219
+
220
+ book, book_id = new_book()
221
+ args = [
222
+ :reverse_assoc,
223
+ Books,
224
+ :belongs_to,
225
+ :author,
226
+ book_id
227
+ ]
228
+
229
+ Ensure 'no author attached' do
230
+ get app.route(*args, :attached)
231
+ is(last_response).ok?
232
+ are(extract_elements).empty?
233
+ end
234
+
235
+ author = Author.create(name: rand(2**64).to_s)
236
+
237
+ Should 'prohibit attaching' do
238
+ post app.route(*args, target_item_id: author.id.to_s)
239
+ is(last_response).readonly_error?
240
+ end
241
+
242
+ Should 'prohibit detaching' do
243
+ delete app.route(*args)
244
+ is(last_response).readonly_error?
245
+ end
246
+ end
247
+ end
248
+
249
+ end
@@ -0,0 +1,269 @@
1
+
2
+ module RearTest__Columns
3
+
4
+ class BasicApp < E
5
+ include Rear
6
+ model Book
7
+ end
8
+ Spec.new BasicApp do
9
+
10
+ item, item_id = new_book()
11
+
12
+ Testing :pane do
13
+ get
14
+ is(last_response).ok?
15
+ expect(extract_elements('.pane-item').size) > 0
16
+
17
+ elements = extract_elements
18
+ expect( elements ).any? {|e| e.text == item[:name].to_s }
19
+ expect( elements ).any? {|e| e.text == item[:about].to_s}
20
+ end
21
+
22
+ Testing :editor do
23
+ get :edit, item_id
24
+ is(last_response).ok?
25
+ elements = extract_elements
26
+ expect( elements ).any? do |e|
27
+ e.children.any? {|c| c.name == 'input' && c.attr(:value) == item[:name].to_s }
28
+ end
29
+ expect( elements ).any? do |e|
30
+ e.children.any? {|c| c.name == 'textarea' && c.text == item[:about].to_s }
31
+ end
32
+ end
33
+ end
34
+
35
+
36
+ class SelectType < E
37
+ include Rear
38
+ model Book
39
+ input :cover, :select do
40
+ options('Solid', 'Soft')
41
+ end
42
+ end
43
+ Spec.new SelectType do
44
+
45
+ item, item_id = new_book( :cover => 'Soft')
46
+
47
+ Testing :pane do
48
+ get
49
+ check( extract_elements ).any? {|e| e.text == 'Soft'}
50
+ end
51
+
52
+ Testing :editor do
53
+ get :edit, item_id
54
+ is(last_response).ok?
55
+ check( extract_elements ).any? { |e|
56
+ e.children.any? do |c|
57
+ c.name == 'select' && c.attr(:name) == 'cover' &&
58
+ c.children.any? {|cc|
59
+ cc.attr(:value) == 'Soft' && cc.attr(:selected)
60
+ }
61
+ end
62
+ }
63
+ end
64
+
65
+ end
66
+
67
+
68
+ class MultiSelectType < E
69
+ include Rear
70
+ model Book
71
+
72
+ on_save do
73
+ params['colors'] = params['colors'].join(',')
74
+ end
75
+
76
+ input :colors, :select, :multiple => true do
77
+ options 'r' => 'Reg', 'g' => 'Green', 'b' => 'Blue' do
78
+ item.colors.to_s.split(',')
79
+ end
80
+ end
81
+ end
82
+ Spec.new MultiSelectType do
83
+
84
+ item, item_id = new_book( :colors => 'r,g')
85
+
86
+ Testing :pane do
87
+
88
+ get
89
+ is(last_response).ok?
90
+ check( extract_elements ).any? {|e|
91
+ e.text =~ /Red/ || e.text =~ /Green/
92
+ }
93
+ end
94
+
95
+ Testing :editor do
96
+ get :edit, item_id
97
+ is(last_response).ok?
98
+ check( extract_elements ).any? { |e|
99
+ e.children.any? do |c|
100
+ c.name == 'select' && c.attr(:name) == 'colors[]' &&
101
+ c.children.any? {|cc|
102
+ cc.attr(:value) == 'r' && cc.attr(:selected)
103
+ } && c.children.any? {|cc|
104
+ cc.attr(:value) == 'g' && cc.attr(:selected)
105
+ }
106
+ end
107
+ }
108
+ end
109
+ end
110
+
111
+
112
+ class CheckboxType < E
113
+ include Rear
114
+ model Book
115
+
116
+ on_save do
117
+ params['colors'] = params['colors'].join(',')
118
+ end
119
+
120
+ input :colors, :checkbox do
121
+ options 'r' => 'Reg', 'g' => 'Green', 'b' => 'Blue' do
122
+ item.colors.to_s.split(',')
123
+ end
124
+ end
125
+ end
126
+ Spec.new CheckboxType do
127
+
128
+ item, item_id = new_book( :colors => 'g,b')
129
+ Testing :pane do
130
+ get
131
+ check( extract_elements ).any? {|e|
132
+ e.text =~ /Green/ || e.text =~ /Blue/
133
+ }
134
+ end
135
+
136
+ Testing :editor do
137
+
138
+ get :edit, item_id
139
+ is(last_response).ok?
140
+ elements = extract_elements('.editor-checkbox_container')
141
+ item.colors.split(',').each do |color|
142
+ check( elements ).any? { |e|
143
+ e.children.any? { |c|
144
+ c.name == 'input' &&
145
+ c.attr(:type) == 'checkbox' &&
146
+ c.attr(:name) == 'colors[]' &&
147
+ c.attr(:value) == color && c.attr(:checked)
148
+ }
149
+ }
150
+ end
151
+ end
152
+ end
153
+
154
+
155
+ class RadioType < E
156
+ include Rear
157
+ model Book
158
+ input :cover, :radio do
159
+ options('Solid', 'Soft') { item.cover }
160
+ end
161
+ end
162
+ Spec.new RadioType do
163
+
164
+ item, item_id = new_book( :cover => 'Solid')
165
+ Testing :pane do
166
+ get
167
+ is(last_response).ok?
168
+ check( extract_elements ).any? {|e| e.text == 'Solid' }
169
+ end
170
+
171
+ Testing :editor do
172
+ get :edit, item_id
173
+ is(last_response).ok?
174
+ elements = extract_elements('.editor-radio_container')
175
+ {'Solid' => true, 'Soft' => false}.each_pair do |value, status|
176
+ check( elements ).any? { |e|
177
+ e.children.any? do |c|
178
+ c.name == 'input' &&
179
+ c.attr(:type) == 'radio' &&
180
+ c.attr(:name) == 'cover' &&
181
+ c.attr(:value) == value &&
182
+ (status ? c.attr(:checked) : true)
183
+ end
184
+ }
185
+ end
186
+ end
187
+ end
188
+
189
+
190
+ class AttrsApp < E
191
+ include Rear
192
+ model Book
193
+ input :name, style: 'width: 100%;'
194
+ input :about, cols: 10
195
+ end
196
+ Spec.new AttrsApp do
197
+
198
+ item, item_id = new_book()
199
+
200
+ get :edit, item_id
201
+ is(last_response).ok?
202
+ elements = extract_elements
203
+ expect( elements ).any? { |e|
204
+ e.children.any? {|c|
205
+ c.name == 'input' && c.attr(:style) == 'width: 100%;'
206
+ }
207
+ }
208
+ expect( elements ).any? { |e|
209
+ e.children.any? {|c| c.name == 'textarea' && c.attr(:cols) == '10' }
210
+ }
211
+
212
+ end
213
+
214
+ class DisablerApp < E
215
+ include Rear
216
+ model Book
217
+
218
+ input :name do
219
+ pane false
220
+ end
221
+
222
+ input :about do
223
+ editor false
224
+ end
225
+
226
+ end
227
+ Spec.new DisablerApp do
228
+
229
+ item, item_id = new_book()
230
+
231
+ Testing :pane do
232
+ get
233
+ is(last_response).ok?
234
+ elements = extract_elements
235
+ Should 'show :about column' do
236
+ expect( elements ).any? {|e|
237
+ e.children.any? {|c| c.to_s =~ /#{item[:about]}/}
238
+ }
239
+ end
240
+ Should 'NOT show :name column' do
241
+ refute do
242
+ elements.any? do |e|
243
+ e.children.any? {|c| c.text == item[:name]}
244
+ end
245
+ end == true
246
+ end
247
+ end
248
+
249
+ Testing :editor do
250
+ get :edit, item_id
251
+ is(last_response).ok?
252
+ elements = extract_elements
253
+ Should 'show :name column' do
254
+ expect( elements ).any? { |e|
255
+ e.children.any? do |c|
256
+ c.name == 'input' && c.attr(:value) == item[:name].to_s
257
+ end
258
+ }
259
+ end
260
+ Should 'NOT show :about column' do
261
+ refute do
262
+ elements.any? do |e|
263
+ e.children.any? {|c| c.name == 'textarea'}
264
+ end
265
+ end == true
266
+ end
267
+ end
268
+ end
269
+ end