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/lib/rear/utils.rb
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
module RearUtils
|
|
2
|
+
include RearConstants
|
|
3
|
+
|
|
4
|
+
# just define controller, do not set model
|
|
5
|
+
def initialize_model_controller model
|
|
6
|
+
return model.const_get(:RearController) if model.const_defined?(:RearController)
|
|
7
|
+
ctrl = model.const_set(:RearController, Class.new(E))
|
|
8
|
+
RearControllerSetup.init(ctrl)
|
|
9
|
+
ctrl.map EUtils.class_to_route(model)
|
|
10
|
+
ctrl.model model
|
|
11
|
+
ctrl
|
|
12
|
+
end
|
|
13
|
+
module_function :initialize_model_controller
|
|
14
|
+
|
|
15
|
+
def associated_model_controller model, ensure_mounted = false
|
|
16
|
+
ctrl = ObjectSpace.each_object(Class).find do |o|
|
|
17
|
+
EUtils.is_app?(o) && o.respond_to?(:model) && o.model == model
|
|
18
|
+
end
|
|
19
|
+
unless ctrl
|
|
20
|
+
ctrl = initialize_model_controller(model)
|
|
21
|
+
ctrl.label(false) # automatically generated controllers not shown in menu
|
|
22
|
+
end
|
|
23
|
+
ctrl.mounted? || ctrl.mount if ensure_mounted
|
|
24
|
+
ctrl
|
|
25
|
+
end
|
|
26
|
+
module_function :associated_model_controller
|
|
27
|
+
|
|
28
|
+
def extract_assocs model, *args
|
|
29
|
+
send('extract_%s_assocs' % orm(model), model, *args)
|
|
30
|
+
end
|
|
31
|
+
module_function :extract_assocs
|
|
32
|
+
|
|
33
|
+
def extract_associated_ar_model model, assoc
|
|
34
|
+
class_name = assoc.options[:class_name] ||
|
|
35
|
+
ActiveSupport::Inflector.camelize(ActiveSupport::Inflector.singularize(assoc.name.to_s))
|
|
36
|
+
if (ns = class_name.to_s.split('::')).size > 1
|
|
37
|
+
return ns.inject(Object) {|n,c| n.const_get c}
|
|
38
|
+
else
|
|
39
|
+
if (ns = model.name.split('::')).size > 1
|
|
40
|
+
ns.pop
|
|
41
|
+
model_namespace = ns.inject(Object) {|n,c| n.const_get c}
|
|
42
|
+
else
|
|
43
|
+
model_namespace = Object
|
|
44
|
+
end
|
|
45
|
+
return model_namespace.const_get(class_name)
|
|
46
|
+
end
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
module_function :extract_associated_ar_model
|
|
50
|
+
|
|
51
|
+
def extract_ar_assocs model, any = false
|
|
52
|
+
model.reflect_on_all_associations.inject(ASSOCS__STRUCT.call) do |map, r|
|
|
53
|
+
|
|
54
|
+
target_model = extract_associated_ar_model(model, r) ||
|
|
55
|
+
raise(NameError, "Was unable to detect model for %s relation" % r)
|
|
56
|
+
|
|
57
|
+
target_pkey = r.options[:primary_key] || target_model.primary_key
|
|
58
|
+
if any || target_pkey # models without primary key not handled by default
|
|
59
|
+
|
|
60
|
+
target_pkey = target_pkey.to_sym if target_pkey
|
|
61
|
+
readonly = nil
|
|
62
|
+
belongs_to_keys = {source: nil, target: nil}
|
|
63
|
+
|
|
64
|
+
assoc_type = case r.macro
|
|
65
|
+
when :belongs_to
|
|
66
|
+
belongs_to_keys[:source] = (r.options[:foreign_key] || ActiveSupport::Inflector.foreign_key(r.name)).to_sym
|
|
67
|
+
belongs_to_keys[:target] = target_pkey
|
|
68
|
+
:belongs_to
|
|
69
|
+
when :has_one
|
|
70
|
+
readonly = true if r.options[:through]
|
|
71
|
+
:has_one
|
|
72
|
+
else
|
|
73
|
+
:has_many
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
map[assoc_type].update r.name => {
|
|
77
|
+
type: assoc_type,
|
|
78
|
+
name: r.name,
|
|
79
|
+
remote_model: target_model,
|
|
80
|
+
remote_pkey: target_pkey,
|
|
81
|
+
readonly: readonly,
|
|
82
|
+
dom_id: dom_id_generator(model, assoc_type, r.name),
|
|
83
|
+
belongs_to_keys: belongs_to_keys
|
|
84
|
+
}.freeze
|
|
85
|
+
end
|
|
86
|
+
map
|
|
87
|
+
end.freeze
|
|
88
|
+
end
|
|
89
|
+
module_function :extract_ar_assocs
|
|
90
|
+
|
|
91
|
+
def extract_dm_assocs model, any = false
|
|
92
|
+
model.relationships.entries.inject(ASSOCS__STRUCT.call) do |map, r|
|
|
93
|
+
_, target_pkey = extract_dm_columns(r.target_model)
|
|
94
|
+
if any || target_pkey # only models with a primary key are handled by default
|
|
95
|
+
|
|
96
|
+
readonly = nil
|
|
97
|
+
belongs_to_keys = {source: nil, target: nil}
|
|
98
|
+
|
|
99
|
+
assoc_type = case r.class.name.split('::')[2]
|
|
100
|
+
when 'ManyToOne'
|
|
101
|
+
belongs_to_keys[:source] = r.source_key.first.name.to_sym
|
|
102
|
+
belongs_to_keys[:target] = r.target_key.first.name.to_sym
|
|
103
|
+
:belongs_to
|
|
104
|
+
when 'OneToOne'
|
|
105
|
+
readonly = true if r.options[:through]
|
|
106
|
+
:has_one
|
|
107
|
+
else
|
|
108
|
+
:has_many
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
map[assoc_type].update r.name => {
|
|
112
|
+
type: assoc_type,
|
|
113
|
+
name: r.name,
|
|
114
|
+
remote_model: r.target_model,
|
|
115
|
+
remote_pkey: target_pkey,
|
|
116
|
+
readonly: readonly,
|
|
117
|
+
dom_id: dom_id_generator(model, assoc_type, r.name),
|
|
118
|
+
belongs_to_keys: belongs_to_keys
|
|
119
|
+
}.freeze
|
|
120
|
+
end
|
|
121
|
+
map
|
|
122
|
+
end.freeze
|
|
123
|
+
end
|
|
124
|
+
module_function :extract_dm_assocs
|
|
125
|
+
|
|
126
|
+
def extract_sq_assocs model, any = false
|
|
127
|
+
model.associations.inject(ASSOCS__STRUCT.call) do |map,an|
|
|
128
|
+
a = model.association_reflection(an)
|
|
129
|
+
|
|
130
|
+
target_model = extract_constant(a[:class_name])
|
|
131
|
+
_, target_pkey = extract_sq_columns(target_model)
|
|
132
|
+
|
|
133
|
+
if any || target_pkey # only models with a primary key are handled by default
|
|
134
|
+
readonly = nil
|
|
135
|
+
belongs_to_keys = {source: nil, target: nil}
|
|
136
|
+
|
|
137
|
+
assoc_type = case a[:type]
|
|
138
|
+
when :many_to_one
|
|
139
|
+
belongs_to_keys[:source] = a[:key]
|
|
140
|
+
belongs_to_keys[:target] = a[:primary_key] || target_pkey
|
|
141
|
+
:belongs_to
|
|
142
|
+
when :one_to_one
|
|
143
|
+
readonly = true if a[:through]
|
|
144
|
+
:has_one
|
|
145
|
+
else
|
|
146
|
+
:has_many
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
map[assoc_type].update a[:name] => {
|
|
150
|
+
type: assoc_type,
|
|
151
|
+
name: a[:name],
|
|
152
|
+
remote_model: target_model,
|
|
153
|
+
remote_pkey: target_pkey,
|
|
154
|
+
readonly: readonly,
|
|
155
|
+
dom_id: dom_id_generator(model, assoc_type, a[:name]),
|
|
156
|
+
belongs_to_keys: belongs_to_keys
|
|
157
|
+
}.freeze
|
|
158
|
+
end
|
|
159
|
+
map
|
|
160
|
+
end.freeze
|
|
161
|
+
end
|
|
162
|
+
module_function :extract_sq_assocs
|
|
163
|
+
|
|
164
|
+
def extract_columns model
|
|
165
|
+
send('extract_%s_columns' % orm(model), model)
|
|
166
|
+
end
|
|
167
|
+
module_function :extract_columns
|
|
168
|
+
|
|
169
|
+
def extract_ar_columns model
|
|
170
|
+
unless model.table_exists?
|
|
171
|
+
puts "WARN: %s table does not exists!" % model.table_name
|
|
172
|
+
return [[], :id]
|
|
173
|
+
end
|
|
174
|
+
pkey = nil
|
|
175
|
+
columns = model.columns.inject([]) do |f,c|
|
|
176
|
+
name, type = c.name.to_sym, c.type.to_s.downcase.to_sym
|
|
177
|
+
type = COLUMNS__DEFAULT_TYPE unless COLUMNS__HANDLED_TYPES.include?(type)
|
|
178
|
+
(c.primary && pkey = name) ? f : f << [name, type]
|
|
179
|
+
end
|
|
180
|
+
[columns.freeze, pkey]
|
|
181
|
+
end
|
|
182
|
+
module_function :extract_ar_columns
|
|
183
|
+
|
|
184
|
+
def extract_dm_columns model
|
|
185
|
+
pkey = nil
|
|
186
|
+
# TODO: find a way to handle Enum columns
|
|
187
|
+
columns = model.properties.inject([]) do |f,c|
|
|
188
|
+
name, type = c.name.to_sym, c.class.name.to_s.split('::').last.downcase.to_sym
|
|
189
|
+
type = COLUMNS__DEFAULT_TYPE unless [:serial].concat(COLUMNS__HANDLED_TYPES).include?(type)
|
|
190
|
+
(type == :serial && pkey = name) ? f : f << [name, type]
|
|
191
|
+
end
|
|
192
|
+
[columns.freeze, pkey]
|
|
193
|
+
end
|
|
194
|
+
module_function :extract_dm_columns
|
|
195
|
+
|
|
196
|
+
def extract_sq_columns model
|
|
197
|
+
pkey = nil
|
|
198
|
+
columns = model.db_schema.inject([]) do |map,(n,s)|
|
|
199
|
+
if s[:primary_key]
|
|
200
|
+
pkey = n
|
|
201
|
+
map
|
|
202
|
+
else
|
|
203
|
+
type = s[:db_type].to_s.split(/\W/).first.to_s.downcase.to_sym
|
|
204
|
+
unless COLUMNS__HANDLED_TYPES.include?(type)
|
|
205
|
+
type = s[:type].to_s.downcase.to_sym
|
|
206
|
+
unless COLUMNS__HANDLED_TYPES.include?(type)
|
|
207
|
+
type = COLUMNS__DEFAULT_TYPE
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
map << [n, type]
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
[columns.freeze, pkey]
|
|
214
|
+
end
|
|
215
|
+
module_function :extract_sq_columns
|
|
216
|
+
|
|
217
|
+
def quote_column model, column
|
|
218
|
+
send('quote_%s_column' % orm(model), model, column)
|
|
219
|
+
end
|
|
220
|
+
module_function :quote_column
|
|
221
|
+
|
|
222
|
+
def quote_ar_column model, column
|
|
223
|
+
model.connection.quote_column_name(column)
|
|
224
|
+
end
|
|
225
|
+
module_function :quote_ar_column
|
|
226
|
+
|
|
227
|
+
def quote_dm_column model, column
|
|
228
|
+
property = model.properties.find {|p| p.name == column}
|
|
229
|
+
model.repository(model.repository_name).adapter.property_to_column_name(property, false)
|
|
230
|
+
end
|
|
231
|
+
module_function :quote_dm_column
|
|
232
|
+
|
|
233
|
+
def quote_sq_column model, column
|
|
234
|
+
model.db.quote_identifier(column)
|
|
235
|
+
end
|
|
236
|
+
module_function :quote_sq_column
|
|
237
|
+
|
|
238
|
+
def ar? model
|
|
239
|
+
[:connection, :columns, :reflect_on_all_associations].all? do |m|
|
|
240
|
+
model.respond_to?(m)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
module_function :ar?
|
|
244
|
+
|
|
245
|
+
def dm? model
|
|
246
|
+
[:repository, :properties, :relationships].all? do |m|
|
|
247
|
+
model.respond_to?(m)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
module_function :dm?
|
|
251
|
+
|
|
252
|
+
def sq? model
|
|
253
|
+
[:db_schema, :columns, :dataset, :associations].all? do |m|
|
|
254
|
+
model.respond_to?(m)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
module_function :sq?
|
|
258
|
+
|
|
259
|
+
def orm model
|
|
260
|
+
[:ar, :dm, :sq].find {|o| send('%s?' % o, model)}
|
|
261
|
+
end
|
|
262
|
+
alias is_orm? orm
|
|
263
|
+
module_function :orm
|
|
264
|
+
module_function :is_orm?
|
|
265
|
+
|
|
266
|
+
def number_with_delimiter n
|
|
267
|
+
n.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, '\1,')
|
|
268
|
+
end
|
|
269
|
+
module_function :number_with_delimiter
|
|
270
|
+
|
|
271
|
+
def normalize_html_attrs attrs
|
|
272
|
+
(attrs||{}).inject({}) {|h,(k,v)| h.merge k.to_s.downcase => v}
|
|
273
|
+
end
|
|
274
|
+
module_function :normalize_html_attrs
|
|
275
|
+
|
|
276
|
+
def dom_id_generator *args
|
|
277
|
+
(args + [args.__id__]).flatten.join('__').gsub(/\W+/, '')
|
|
278
|
+
end
|
|
279
|
+
module_function :dom_id_generator
|
|
280
|
+
|
|
281
|
+
def extract_constant smth
|
|
282
|
+
return Object.const_get(smth) if smth.is_a?(Symbol)
|
|
283
|
+
return smth.sub('::', '').split('::').inject(Object) {|o,c| o.const_get(c)} if smth.is_a?(String)
|
|
284
|
+
smth
|
|
285
|
+
end
|
|
286
|
+
module_function :extract_constant
|
|
287
|
+
|
|
288
|
+
end
|
data/rear.gemspec
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
version = '0.2.0'
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
|
|
6
|
+
s.name = 'rear'
|
|
7
|
+
s.version = version
|
|
8
|
+
s.authors = ['Silviu Rusu']
|
|
9
|
+
s.email = ['slivuz@gmail.com']
|
|
10
|
+
s.homepage = 'https://github.com/espresso/rear'
|
|
11
|
+
s.summary = 'rear-%s' % version
|
|
12
|
+
s.description = 'ORM-agnostic CRUDifier for Espresso Framework'
|
|
13
|
+
|
|
14
|
+
s.required_ruby_version = '>= 1.9.2'
|
|
15
|
+
|
|
16
|
+
s.add_dependency 'e', '>= 0.4.8'
|
|
17
|
+
s.add_dependency 'el', '>= 0.4.8'
|
|
18
|
+
s.add_dependency 'slim', '>= 2.0'
|
|
19
|
+
|
|
20
|
+
s.add_development_dependency 'bundler'
|
|
21
|
+
|
|
22
|
+
s.require_paths = ['lib']
|
|
23
|
+
s.files = Dir['**/{*,.[a-z]*}'].reject {|e| e =~ /\.(gem|lock)\Z/}
|
|
24
|
+
s.executables = ['rear']
|
|
25
|
+
|
|
26
|
+
s.licenses = ['MIT']
|
|
27
|
+
end
|
data/test/helpers.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module EASpecHelpers
|
|
2
|
+
|
|
3
|
+
def new_book data = {}
|
|
4
|
+
data = {name: random_string, about: random_string}.merge(data)
|
|
5
|
+
item = Book.create(data)
|
|
6
|
+
item_id = item[:id].to_i
|
|
7
|
+
check(item_id) > 0
|
|
8
|
+
[item, item_id]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def count_books conditions = nil
|
|
12
|
+
args = conditions ? [{conditions: conditions}] : []
|
|
13
|
+
RearORM.new(Book).count *args
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def extract_elements selector = nil
|
|
17
|
+
selector ||= last_request.env['PATH_INFO'] =~ /\/+edit\/+\d+/ ?
|
|
18
|
+
'.editor-column_value' : '.pane-column_value'
|
|
19
|
+
doc = Nokogiri::HTML(last_response.body)
|
|
20
|
+
columns = doc.css(selector)
|
|
21
|
+
columns
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def readonly_error? last_response
|
|
25
|
+
is(last_response).client_error?
|
|
26
|
+
does(last_response.body) =~ /readonly/i
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def random_string
|
|
30
|
+
('A'..'Z').to_a.sample(5).join + [rand.to_s, rand.to_s].sample
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
data/test/models/ar.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
|
|
2
|
+
|
|
3
|
+
class Author < ActiveRecord::Base
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Genre < ActiveRecord::Base
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Book < ActiveRecord::Base
|
|
10
|
+
belongs_to :author
|
|
11
|
+
has_and_belongs_to_many :genres
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class State < ActiveRecord::Base
|
|
15
|
+
has_many :cities, foreign_key: :state_code, primary_key: :code
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class City < ActiveRecord::Base
|
|
19
|
+
belongs_to :state, foreign_key: :state_code, primary_key: :code
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
ActiveRecord::Migration.create_table :books_genres do |t|
|
|
23
|
+
t.integer :book_id
|
|
24
|
+
t.integer :genre_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
ActiveRecord::Migration.create_table :genres do |t|
|
|
28
|
+
t.string :name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
ActiveRecord::Migration.create_table :authors do |t|
|
|
32
|
+
t.string :name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
ActiveRecord::Migration.create_table :books do |t|
|
|
36
|
+
t.integer :author_id
|
|
37
|
+
t.string :name
|
|
38
|
+
t.text :about
|
|
39
|
+
t.string :cover
|
|
40
|
+
t.string :colors
|
|
41
|
+
t.date :created_at
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
ActiveRecord::Migration.create_table :states do |t|
|
|
45
|
+
t.string :name
|
|
46
|
+
t.string :code
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ActiveRecord::Migration.create_table :cities do |t|
|
|
50
|
+
t.string :name
|
|
51
|
+
t.string :state_code
|
|
52
|
+
end
|
data/test/models/dm.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
DataMapper.setup :default, 'sqlite::memory:'
|
|
2
|
+
|
|
3
|
+
class Author
|
|
4
|
+
include DataMapper::Resource
|
|
5
|
+
|
|
6
|
+
property :id, Serial
|
|
7
|
+
property :name, String
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Genre
|
|
11
|
+
include DataMapper::Resource
|
|
12
|
+
|
|
13
|
+
property :id, Serial
|
|
14
|
+
property :name, String
|
|
15
|
+
|
|
16
|
+
has n, :books, through: Resource
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Book
|
|
20
|
+
include DataMapper::Resource
|
|
21
|
+
|
|
22
|
+
property :id, Serial
|
|
23
|
+
property :name, String
|
|
24
|
+
property :about, Text
|
|
25
|
+
property :cover, String
|
|
26
|
+
property :colors, String
|
|
27
|
+
property :created_at, Date
|
|
28
|
+
|
|
29
|
+
belongs_to :author, required: false
|
|
30
|
+
has n, :genres, through: Resource
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class State
|
|
34
|
+
include DataMapper::Resource
|
|
35
|
+
|
|
36
|
+
property :id, Serial
|
|
37
|
+
property :name, String, index: true
|
|
38
|
+
property :code, String, unique: true, length: 2
|
|
39
|
+
|
|
40
|
+
has n, :cities, child_key: :state_code, parent_key: :code
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class City
|
|
44
|
+
include DataMapper::Resource
|
|
45
|
+
|
|
46
|
+
property :id, Serial
|
|
47
|
+
property :name, String, index: true
|
|
48
|
+
|
|
49
|
+
belongs_to :state, child_key: :state_code, parent_key: :code, required: false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
DataMapper.finalize
|
|
53
|
+
DataMapper.auto_migrate!
|