wedge 0.1.17 → 0.1.18

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