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