wedge 0.1.17 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +8 -1
  3. data/Makefile +2 -0
  4. data/Rakefile +8 -4
  5. data/TODO.md +4 -0
  6. data/lib/roda/plugins/wedge.rb +14 -96
  7. data/lib/wedge.rb +9 -4
  8. data/lib/wedge/component.rb +70 -29
  9. data/lib/wedge/config.rb +36 -6
  10. data/lib/wedge/middleware.rb +43 -35
  11. data/lib/wedge/opal.rb +15 -4
  12. data/lib/wedge/plugins/ability_list.rb +95 -0
  13. data/lib/wedge/plugins/current_user.rb +48 -0
  14. data/lib/wedge/plugins/factory.rb +36 -0
  15. data/lib/wedge/plugins/form.rb +216 -343
  16. data/lib/wedge/plugins/{validations.rb → form/validations.rb} +64 -37
  17. data/lib/wedge/plugins/form_backup.rb +442 -0
  18. data/lib/wedge/plugins/render.rb +110 -0
  19. data/lib/wedge/plugins/uploader.rb +2 -2
  20. data/lib/wedge/utilis/duplicable.rb +104 -0
  21. data/lib/wedge/utilis/hash.rb +48 -0
  22. data/lib/wedge/version.rb +1 -1
  23. data/playground/app/app.rb +27 -11
  24. data/playground/app/components/abilities.rb +11 -0
  25. data/playground/app/components/current_user.rb +12 -0
  26. data/playground/app/components/layout.rb +5 -0
  27. data/playground/app/components/todo_list.rb +24 -0
  28. data/playground/app/config/boot.rb +3 -0
  29. data/playground/app/forms/todo_list_add.rb +9 -0
  30. data/playground/app/models/user.rb +5 -0
  31. data/playground/public/css/styles.css +139 -0
  32. data/playground/public/css/todo_list.css +138 -0
  33. data/playground/public/todo_list.html +23 -0
  34. data/playground/src/css/styles.scss +2 -1
  35. data/playground/src/css/todo_list.scss +165 -0
  36. data/playground/src/todo_list.slim +17 -0
  37. data/spec/playground/uploader_spec.rb +27 -29
  38. data/spec/spec_helper.rb +29 -0
  39. data/spec/stubs/models/user.rb +15 -0
  40. data/spec/wedge/plugins/current_user_spec.rb +29 -0
  41. data/spec/wedge/plugins/factory_spec.rb +16 -0
  42. data/spec/wedge/plugins/form_spec.rb +119 -0
  43. data/spec/wedge/plugins/uploader_spec.rb +1 -3
  44. data/spec/wedge_spec.rb +3 -3
  45. metadata +28 -3
@@ -50,25 +50,7 @@ class Wedge
50
50
  # :date => [:format] }
51
51
  #
52
52
  module Validations
53
- def server? &block
54
- RUBY_ENGINE == 'ruby'
55
- end
56
- alias :server :server?
57
-
58
- def client?
59
- RUBY_ENGINE == 'opal'
60
- end
61
- alias :client :client?
62
-
63
- def self.server? &block
64
- RUBY_ENGINE == 'ruby'
65
- end
66
- alias :server :server?
67
-
68
- def self.client?
69
- RUBY_ENGINE == 'opal'
70
- end
71
- alias :client :client?
53
+ include Methods
72
54
 
73
55
  # Check if the current model state is valid. Each call to {#valid?} will
74
56
  # reset the {#errors} array.
@@ -88,18 +70,40 @@ class Wedge
88
70
  # end
89
71
  #
90
72
  def valid?
91
- errors.clear
73
+ _errors.clear
92
74
  validate
93
- errors.empty?
75
+ _errors.empty?
94
76
  end
95
77
 
96
78
  # Base validate implementation. Override this method in subclasses.
97
79
  def validate
98
80
  end
99
81
 
100
- # Hash of errors for each attribute in this model.
82
+ def error key, value
83
+ value = [value] unless value.is_a? Array
84
+ _errors[key] = value
85
+ end
86
+
101
87
  def errors
102
- @errors ||= Hash.new { |hash, key| hash[key] = [] }
88
+ IndifferentHash.new(_errors)
89
+ end
90
+
91
+ # gives back errors using the model_alias keys
92
+ def model_errors
93
+ IndifferentHash.new(_model_errors)
94
+ end
95
+
96
+ # Hash of errors for each attribute in this model.
97
+ def _errors
98
+ @_errors ||= Hash.new do |hash, key|
99
+ data = _accessor_options[key].key?(:form) ? {} : []
100
+ alias_key = _aliases[key] || key
101
+ _model_errors[alias_key] = hash[key] = data
102
+ end
103
+ end
104
+
105
+ def _model_errors
106
+ @_model_errors ||= {}
103
107
  end
104
108
 
105
109
  protected
@@ -115,7 +119,7 @@ class Wedge
115
119
  # @param [Array<Symbol, Symbol>] error The error that should be returned
116
120
  # when the validation fails.
117
121
  def assert_format(att, format, error = [att, :format])
118
- if assert_present(att, error)
122
+ if !send(att).to_s.empty?
119
123
  assert(_attributes.send(att).to_s.match(format), error)
120
124
  end
121
125
  end
@@ -130,25 +134,35 @@ class Wedge
130
134
  if att.is_a? Array
131
135
  att.each { |a| assert_present(a, error = [a, :not_present])}
132
136
  else
133
- if form_name = _form[att]
134
- options = {}
135
- options[:key] = _options[:key] if _options.key? :key
137
+ att_options = _accessor_options[att].deep_dup
136
138
 
137
- f = wedge(form_name, _attributes.send(att).attributes, options)
138
- assert(f.valid?, [att, f.errors])
139
+ if att_options.key? :form
140
+ assert_form att, error
139
141
  else
140
- assert(!_attributes.send(att).to_s.empty?, error)
142
+ assert(!send(att).to_s.empty?, error)
141
143
  end
142
144
  end
143
145
  end
144
146
 
147
+ def assert_form(att, error = [att, :no_form])
148
+ att_options = _accessor_options[att].deep_dup
149
+ form_name = att_options.delete :form
150
+
151
+ f = wedge("#{form_name}_form", send(att).attributes, att_options)
152
+ assert(f.valid?, [att, f._errors])
153
+ end
154
+
145
155
  # Checks if all the characters of an attribute is a digit.
146
156
  #
147
157
  # @param [Symbol] att The attribute you wish to verify the numeric format.
148
158
  # @param [Array<Symbol, Symbol>] error The error that should be returned
149
159
  # when the validation fails.
150
160
  def assert_numeric(att, error = [att, :not_numeric])
151
- if assert_present(att, error)
161
+ # discuss: I commented this out as I don't think we should assume they
162
+ # want to validate presents if they validate for numeric. if they
163
+ # validate for numeric.
164
+ # if assert_present(att, error)
165
+ if !send(att).to_s.empty?
152
166
  if client?
153
167
  assert_format(att, /^\-?\d+$/, error)
154
168
  else
@@ -158,13 +172,13 @@ class Wedge
158
172
  end
159
173
 
160
174
  if client?
161
- URL = /^(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(2 5[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3} |localhost)(:[0-9]{1,5})?(\/.*)?$/i
175
+ URL = /^(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|localhost)(:[0-9]{1,5})?(\/.*)?$/i
162
176
  else
163
- URL = /\A(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(2 5[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3} |localhost)(:[0-9]{1,5})?(\/.*)?\z/i
177
+ URL = /\A(http|https):\/\/([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}|(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}|localhost)(:[0-9]{1,5})?(\/.*)?\z/i
164
178
  end
165
179
 
166
180
  def assert_url(att, error = [att, :not_url])
167
- if assert_present(att, error)
181
+ if !send(att).to_s.empty?
168
182
  assert_format(att, URL, error)
169
183
  end
170
184
  end
@@ -176,7 +190,7 @@ class Wedge
176
190
  end
177
191
 
178
192
  def assert_email(att, error = [att, :not_email])
179
- if assert_present(att, error)
193
+ if !send(att).to_s.empty?
180
194
  assert_format(att, EMAIL, error)
181
195
  end
182
196
  end
@@ -186,7 +200,7 @@ class Wedge
186
200
  end
187
201
 
188
202
  def assert_length(att, range, error = [att, :not_in_range])
189
- if assert_present(att, error)
203
+ if !send(att).to_s.empty?
190
204
  val = _attributes.send(att).to_s
191
205
  assert range.include?(val.length), error
192
206
  end
@@ -243,7 +257,20 @@ class Wedge
243
257
  # end
244
258
  # end
245
259
  def assert(value, error)
246
- value or errors[error.first].push(error.last) && false
260
+ value or begin
261
+ name = error.shift.to_s
262
+ atts = _accessor_options[name][:atts] || false
263
+ error = atts ? error.first.select {|k, _| atts.include?(k) } : error
264
+ errors = _errors[name]
265
+
266
+ if errors.is_a?(Array)
267
+ errors.concat(error) && false
268
+ else
269
+ errors.merge!(error.is_a?(Array) ? error.first : error) && false
270
+ end
271
+
272
+ false
273
+ end
247
274
  end
248
275
  end
249
276
  end
@@ -0,0 +1,442 @@
1
+ require 'wedge/plugins/validations'
2
+ require 'forwardable'
3
+
4
+ class Wedge
5
+ module Plugins
6
+ class Form < Component
7
+ name :form_plugin
8
+
9
+ include Methods
10
+ include Validations
11
+
12
+ module Delegates
13
+ def _delegates(*names)
14
+ accessors = Module.new do
15
+ extend Forwardable # DISCUSS: do we really need Forwardable here?
16
+ names.each do |name|
17
+ delegate [name, "#{name}="] => :_attributes
18
+ end
19
+ end
20
+ include accessors
21
+ end
22
+ end
23
+
24
+ extend Delegates
25
+
26
+ class Attributes
27
+ def set_values(atts)
28
+ @_attributes = []
29
+
30
+ atts.each do |key, val|
31
+ if respond_to?("#{key}=")
32
+ send(:"#{key}=", val)
33
+ @_attributes << key
34
+ end
35
+ end
36
+ end
37
+
38
+ def set_attr_accessors attrs
39
+ attrs.each do |attr|
40
+ define_singleton_method "#{attr}=" do |value|
41
+ value = value.to_obj if value.is_a? Hash
42
+ instance_variable_set(:"@#{attr}", value)
43
+ @_attributes ||= []
44
+ @_attributes << attr
45
+ end
46
+
47
+ define_singleton_method attr do
48
+ instance_variable_get(:"@#{attr}")
49
+ end
50
+ end
51
+ end
52
+
53
+ def _attributes
54
+ @_attributes ||= []
55
+ end
56
+
57
+ def empty?
58
+ _attributes.empty?
59
+ end
60
+ end
61
+
62
+ # Initialize with a hash of attributes and values.
63
+ # If extra attributes are sent, a NoMethodError exception will be raised.
64
+ #
65
+ # @example
66
+ #
67
+ # class EditPost < Scrivener
68
+ # attr_accessor :title
69
+ # attr_accessor :body
70
+ #
71
+ # def validate
72
+ # assert_present :title
73
+ # assert_present :body
74
+ # end
75
+ # end
76
+ #
77
+ # edit = EditPost.new(title: "Software Tools")
78
+ #
79
+ # edit.valid? #=> false
80
+ #
81
+ # edit.errors[:title] #=> []
82
+ # edit.errors[:body] #=> [:not_present]
83
+ #
84
+ # edit.body = "Recommended reading..."
85
+ #
86
+ # edit.valid? #=> true
87
+ #
88
+ # # Now it's safe to initialize the model.
89
+ # post = Post.new(edit.attributes)
90
+ # post.save
91
+ def initialize(atts = {}, options = {})
92
+ @_data = atts
93
+ @_data = atts.to_obj if atts.is_a? Hash
94
+ @_options = options
95
+
96
+ # @_attributes = Class.new(Attributes).new
97
+ @_attributes = Attributes.new
98
+ @_attributes.set_attr_accessors _attr_accessors
99
+ @_attributes.set_values _data
100
+
101
+ _data.each do |key, val|
102
+ send("#{key}=", val)
103
+ end
104
+
105
+ _form.each do |key, form_name|
106
+ opts = {}
107
+ if _data.respond_to?(key)
108
+ opts[key] = wedge(form_name, _data.send(key))
109
+ end
110
+ @_attributes.set_values opts
111
+
112
+ send("#{key}=", opts[key])
113
+ end
114
+ end
115
+
116
+ def self.attr_accessor(*vars)
117
+ @_attr_accessors ||= []
118
+ @_form ||= {}
119
+
120
+ vars.each do |v|
121
+ if !v.is_a? Hash
122
+ @_attr_accessors << v unless @_attr_accessors.include? v
123
+ else
124
+ v = v.first
125
+
126
+ unless @_attr_accessors.include? v.first
127
+ @_attr_accessors << v.first
128
+ @_form[v.first] = v.last
129
+ end
130
+ end
131
+ end
132
+
133
+ _delegates(*_attr_accessors)
134
+ end
135
+
136
+ def method_missing method, *args, &block
137
+ # respond_to?(symbol, include_all=false)
138
+ if _data.respond_to? method, true
139
+ _data.send method, *args, &block
140
+ else
141
+ return if method[/\=\z/]
142
+
143
+ super
144
+ end
145
+ end
146
+
147
+ # Return hash of attributes and values.
148
+ def attributes
149
+ Hash.new.tap do |atts|
150
+ _attributes.instance_variables.each do |ivar|
151
+ # todo: figure out why it's setting @constructor and @toString
152
+ next if ivar == :@constructor || ivar == :@toString || ivar == :@_attributes || ivar == :@_data || ivar == :@_forms
153
+
154
+ att = ivar[1..-1].to_sym
155
+ atts[att] = _attributes.send(att)
156
+
157
+ if form_name = _form[att.to_s.to_sym]
158
+ atts[att] = wedge(form_name, atts[att].respond_to?(:attributes)? atts[att].attributes : atts[att]).attributes
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def model_attributes data = attributes
165
+ hash = {}
166
+
167
+ data.each do |k, v|
168
+ if form_name = _form[k.to_s.to_sym]
169
+ d = data[k]
170
+ d = d.attributes if d.is_a?(Form)
171
+
172
+ f = wedge(form_name, d)
173
+ k = "#{k}_attributes"
174
+ dt = f.model_attributes
175
+
176
+ hash[k] = model_attributes dt
177
+ elsif v.is_a? Hash
178
+ hash[k] = model_attributes data[k]
179
+ else
180
+ hash[k] = v
181
+ end
182
+ end
183
+
184
+ hash
185
+ end
186
+
187
+ def slice(*keys)
188
+ Hash.new.tap do |atts|
189
+ keys.each do |att|
190
+ atts[att] = send(att)
191
+ # atts[att] = _attributes.send(att)
192
+ end
193
+ end
194
+ end
195
+
196
+ def display_errors options = {}, &block
197
+ dom = options.delete(:dom) || _dom
198
+ d_errors = errors
199
+
200
+ if override_errors = options[:override_errors]
201
+ d_errors = override_errors
202
+ end
203
+
204
+ keys = options.delete(:keys) || (_options[:key] ? [_options[:key]] : [])
205
+
206
+ if extra_errors = options.delete(:errors)
207
+ extra_errors.each do |key, value|
208
+ d_errors[key] = value
209
+ end
210
+ end
211
+
212
+ d_errors.each do |key, error|
213
+ d_keys = (keys.dup << key)
214
+
215
+ error = error.first
216
+
217
+ if error.is_a?(Hash)
218
+ d_options = options.dup
219
+ d_options[:keys] = d_keys
220
+ d_options[:override_errors] = d_errors[key].first
221
+
222
+ display_errors d_options, &block
223
+ elsif !block_given? || block.call(d_keys, error) == false
224
+ name = d_keys.each_with_index.map do |field, i|
225
+ i != 0 ? "[#{field}]" : field
226
+ end.join
227
+
228
+ if tmpl = options[:tmpl]
229
+ if client?
230
+ field_error_dom = DOM.new(`#{tmpl.dom}[0].outerHTML`)
231
+ else
232
+ field_error_dom = DOM.new(tmpl.dom.to_html)
233
+ end
234
+ else
235
+ field_error_dom = DOM.new('<span class="field-error"><span>')
236
+ end
237
+
238
+ field_error_dom.html _error_name(key, error)
239
+
240
+ field = dom.find("[name='#{name}']")
241
+ field.before field_error_dom.dom
242
+ end
243
+ end
244
+ end
245
+ alias_method :render_errors, :display_errors
246
+
247
+ def render_values dom = false, key = false, data = false
248
+ dom = _options[:dom] unless dom
249
+ key = _options[:key] if !key && _options.key?(:key)
250
+
251
+ dom.find('input, select, textarea') do |element|
252
+ name = element['name']
253
+ next if name.nil?
254
+ name = name.gsub(/\A#{key}/, '') if key
255
+ keys = name.gsub(/\A\[/, '').gsub(/[^a-z0-9_]/, '|').gsub(/\|\|/, '|').gsub(/\|$/, '').split('|')
256
+ value = false
257
+
258
+ keys.each do |k|
259
+ begin
260
+ value = value != false ? value.send(k) : send(k)
261
+ rescue
262
+ value = ''
263
+ end
264
+ end
265
+
266
+ case element.name
267
+ when 'select'
268
+ element.find('option') do |x|
269
+ x['selected'] = true if x['value'] == value.to_s
270
+ end
271
+ when 'input'
272
+ if %w(radio checkbox).include? element['type']
273
+ if element['value'] == value.to_s
274
+ element['checked'] = true
275
+ else
276
+ element.delete 'checked'
277
+ end
278
+ else
279
+ value = sprintf('%.2f', value) if value.is_a? BigDecimal
280
+ element['value'] = value.to_s
281
+ end
282
+ when 'textarea'
283
+ element.val value.to_s
284
+ end
285
+ end
286
+ end
287
+
288
+ def _attributes
289
+ @_attributes ||= {}
290
+ end
291
+
292
+ def validate_msg error, column
293
+ false
294
+ end
295
+
296
+ protected
297
+
298
+ def _data
299
+ @_data ||= {}
300
+ end
301
+
302
+ def self._attr_accessors
303
+ @_attr_accessors ||= []
304
+ end
305
+
306
+ def self._form
307
+ @_form || {}
308
+ end
309
+
310
+ def _form
311
+ self.class._form
312
+ end
313
+
314
+ def _attr_accessors
315
+ self.class._attr_accessors
316
+ end
317
+
318
+ def _options
319
+ @_options
320
+ end
321
+
322
+ def _dom
323
+ @_dom ||= @_options[:dom]
324
+ end
325
+
326
+ def _error_name key, error
327
+ validate_msg(error.to_sym, key.to_sym) || case error.to_s.to_sym
328
+ when :not_email
329
+ 'Email Isn\'t Valid.'
330
+ when :not_present
331
+ 'Required.'
332
+ when :not_equal
333
+ 'Password does not match.'
334
+ else
335
+ !error[/\s/] ? error.to_s.gsub(/_/, ' ').titleize : error
336
+ end
337
+ end
338
+
339
+ def empty?
340
+ _attributes.empty?
341
+ end
342
+
343
+ def wedge_config
344
+ @wedge_config ||= begin
345
+ c = super
346
+ c.skip_method_wrap
347
+ c
348
+ end
349
+ end
350
+
351
+ module InstanceMethods
352
+ def render_fields data, options = {}
353
+ data = data.is_a?(Hash) ? data.to_obj : data
354
+
355
+ l_dom = options[:dom] || dom
356
+
357
+ l_dom.find("[data-if]") do |field_dom|
358
+ value = get_value_for field_dom['data-if'], data
359
+
360
+ unless value.present?
361
+ field_dom.remove
362
+ end
363
+ end
364
+
365
+ l_dom.find("[data-unless]") do |field_dom|
366
+ value = get_value_for field_dom['data-unless'], data
367
+
368
+ if value.present?
369
+ field_dom.remove
370
+ end
371
+ end
372
+
373
+ l_dom.find("[data-field]") do |field_dom|
374
+ if field = field_dom['data-field']
375
+ value = get_value_for field, data
376
+
377
+ if !value.nil?
378
+ value = value.to_s
379
+
380
+ if value != value.upcase && !value.match(Wedge::Plugins::Form::EMAIL)
381
+ field_value = value.titleize
382
+ else
383
+ field_value = value
384
+ end
385
+
386
+ field_value = 'No' if field_value == 'False'
387
+ field_value = 'Yes' if field_value == 'True'
388
+
389
+ field_dom.html = field_value
390
+ else
391
+ field_dom.html = ''
392
+ end
393
+ end
394
+ end
395
+
396
+ l_dom
397
+ end
398
+
399
+ def get_value_for field, data
400
+ field = (field || '').split '.'
401
+
402
+ if field.length > 1
403
+ value = data.is_a?(Hash) ? data.to_obj : data
404
+
405
+ field.each_with_index do |f, i|
406
+ # might not have the parent object
407
+ if (value.respond_to?('empty?') ? value.empty? : !value.present?)
408
+ value = ''
409
+ next
410
+ end
411
+
412
+ if (i+1) < field.length
413
+ begin
414
+ value = value.send(f)
415
+ rescue
416
+ value = nil
417
+ end
418
+ else
419
+ begin
420
+ value = value.respond_to?(:present) ? value.present("print_#{f}") : value.send(f)
421
+ rescue
422
+ value = nil
423
+ end
424
+ end
425
+
426
+ end
427
+ else
428
+ begin
429
+ value = data.respond_to?(:present) ? data.present("print_#{field.first}") : data.send(field.first)
430
+ rescue
431
+ value = nil
432
+ end
433
+ end
434
+
435
+ value
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
441
+
442
+ Wedge::Form = Wedge::Plugins::Form