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
@@ -1,24 +1,47 @@
1
1
  class Wedge
2
2
  class Middleware
3
- def initialize(app, settings = {})
4
- @app = app
5
-
6
- # Add settings to wedge
7
- settings.each do |k, v|
8
- Wedge.config.send "#{k}=", v
3
+ def initialize(app = false, settings = {}, scope = false)
4
+ @app = app
5
+ @scope = scope || self.class.scope
6
+ @opal = Wedge::Opal::Server.new { |s|
7
+ s.prefix = Wedge.config.assets_url
8
+ s.debug = Wedge.config.debug
9
+ s.append_path "#{Dir.pwd}/#{Wedge.config.app_dir}"
10
+ }
11
+
12
+ case settings
13
+ when Proc
14
+ Wedge.config.instance_eval &settings
15
+ else
16
+ settings.each { |k, v| Wedge.config.send "#{k}=", v }
9
17
  end
10
18
  end
11
19
 
12
20
  def call(env)
13
- responder = Responder.new(@app, env)
21
+ responder = Responder.new(@app, @opal, @scope, env)
14
22
  responder.respond
15
23
  end
16
24
 
25
+ class << self
26
+ attr_accessor :scope
27
+
28
+ def scope! scope
29
+ klass = Class.new(self)
30
+ klass.instance_variable_set(:@scope, scope)
31
+ klass
32
+ end
33
+
34
+ def call env
35
+ self.new.call env
36
+ end
37
+ end
38
+
17
39
  class Responder
40
+ attr_reader :opal, :scope
18
41
  attr_accessor :app, :env, :wedge_path, :extension
19
42
 
20
- def initialize(app, env)
21
- @app = app; @env = env
43
+ def initialize(app, opal, scope, env)
44
+ @app = app; @opal = opal; @scope = (scope || self); @env = env
22
45
  end
23
46
 
24
47
  def respond
@@ -26,17 +49,7 @@ class Wedge
26
49
  @wedge_path, @extension = $1, $2
27
50
  body, headers, status = [], {}, 200
28
51
 
29
- case extension
30
- when 'map'
31
- body << ::Wedge.source_map(wedge_path)
32
- when 'rb'
33
- if wedge_path =~ /^wedge/
34
- path = ::Wedge.config.path.gsub(/\/wedge.rb$/, '')
35
- body << File.read("#{path}/#{wedge_path}.rb")
36
- else
37
- body << File.read("#{ROOT_PATH}/#{wedge_path}.rb")
38
- end if Wedge.config.debug
39
- when 'call'
52
+ if extension == 'call'
40
53
  body_data = request.body.read
41
54
  data = request.params
42
55
 
@@ -58,16 +71,17 @@ class Wedge
58
71
  method_args = data.delete(:__wedge_args__)
59
72
 
60
73
  if method_args == '__wedge_data__' && data
61
- method_args = [data]
62
- res = Wedge.scope!(self)[name].send(method_called, *method_args) || ''
74
+ method_args = [data]
75
+ res = Wedge.scope!(scope)[name].send(method_called, *method_args) || ''
63
76
  else
64
77
  # This used to send things like init, we need a better way to
65
78
  # send client config data to the server
66
79
  # res = scope.wedge(name, data).send(method_called, *method_args) || ''
67
- res = Wedge.scope!(self)[name].send(method_called, *method_args) || ''
80
+ res = Wedge.scope!(scope)[name].send(method_called, *method_args) || ''
68
81
  end
69
82
 
70
- # headers["WEDGE-CSRF-TOKEN"] = scope.csrf_token if scope.methods.include? :csrf_token
83
+ # discuss: I don't think we should update the csrf token # every ajax call
84
+ # headers["WEDGE-CSRF-TOKEN"] = self.csrf_token if self.methods.include? :csrf_token
71
85
 
72
86
  if res.is_a? Hash
73
87
  headers["Content-Type"] = 'application/json; charset=UTF-8'
@@ -75,17 +89,11 @@ class Wedge
75
89
  else
76
90
  body << res.to_s
77
91
  end
78
- else
79
- headers['Content-Type'] = 'application/javascript; charset=UTF-8'
80
92
 
81
- if Wedge.config.debug
82
- body << "#{Wedge.javascript(wedge_path)}\n//# sourceMappingURL=#{Wedge.assets_url}/#{wedge_path}.map"
83
- else
84
- body << Wedge.javascript(wedge_path)
85
- end
93
+ [status, headers, body]
94
+ else
95
+ @opal.call env
86
96
  end
87
-
88
- [status, headers, body.join]
89
97
  else
90
98
  response.finish
91
99
  end
@@ -98,7 +106,7 @@ class Wedge
98
106
  private
99
107
 
100
108
  def path
101
- @env['PATH_INFO']
109
+ @env['PATH_INFO'].present?? @env['PATH_INFO'] : @env['PATH_INFO'] = @env['REQUEST_PATH']
102
110
  end
103
111
 
104
112
  def request
@@ -107,7 +115,7 @@ class Wedge
107
115
 
108
116
  def response
109
117
  @response ||= begin
110
- status, headers, body = @app.call(request.env)
118
+ status, headers, body = (@app ? @app.call(request.env) : [404, {}, ''])
111
119
  Rack::Response.new(body, status, headers)
112
120
  end
113
121
  end
data/lib/wedge/opal.rb CHANGED
@@ -34,11 +34,20 @@ unless RUBY_ENGINE == 'opal'
34
34
  @result << Builder.build(path).to_s
35
35
  end
36
36
  elsif comp_class
37
- comp_class.config.on_compile.each { |blk| comp_class.instance_eval(&blk) }
37
+ comp_class.config.on_compile.each { |blk| comp_class.instance_exec(true, &blk) }
38
38
  comp_name = comp_class.config.name
39
39
  compiled_data = Base64.encode64 comp_class.config.client_data.to_json
40
+ js = ''
41
+
42
+ js << "require '#{self.file}'; Wedge.config.component_class[:#{comp_name}].config.data = HashObject.new(Wedge.config.component_class[:#{comp_name}].config.data.to_h.merge JSON.parse(Base64.decode64('#{compiled_data}')))"
43
+ # todo: discuss: pass plugin settings that were set server side?
44
+ js << "; Wedge.plugin(:#{comp_name.to_s.gsub(/_plugin$/, '')})" if comp_class.config.is_plugin
40
45
 
41
- @result << Opal.original_compile("require '#{self.file}'; Wedge.config.component_class[:#{comp_name}].config.data = HashObject.new(Wedge.config.component_class[:#{comp_name}].config.data.to_h.merge JSON.parse(Base64.decode64('#{compiled_data}')))")
46
+ @result << Opal.original_compile(js)
47
+
48
+ if compile_str = comp_class.config.compile_str
49
+ @result << compile_str
50
+ end
42
51
 
43
52
  load_requires logical_path
44
53
  end
@@ -74,10 +83,12 @@ end
74
83
 
75
84
  if RUBY_ENGINE == 'opal'
76
85
  class Element
77
- alias_native :mask
86
+ # alias_native :mask
78
87
  alias_native :remove_data, :removeData
79
88
  alias_native :replace_with, :replaceWith
80
- alias_native :selectize
89
+ # alias_native :selectize
81
90
  end
91
+ else
92
+ Wedge::Opal.use_gem 'wedge'
82
93
  end
83
94
 
@@ -0,0 +1,95 @@
1
+ require 'wedge/plugins/form'
2
+
3
+ class Wedge
4
+ module Plugins
5
+ class AbilityList < Form
6
+ name :ability_list_plugin
7
+
8
+ Error = Class.new(StandardError)
9
+
10
+ # Returns a list of rules. These are populated by `can` and `cannot`.
11
+ # (Rules are tuples)
12
+ def rules
13
+ @rules ||= []
14
+ end
15
+
16
+ # ---
17
+
18
+ # Declares that the owner can perform `verb` on `class`.
19
+ def can(verb, klass=nil, columns=[], &block)
20
+ columns = [columns] unless columns.is_a? Array
21
+ rules << [true, verb, get_class(klass), columns, block]
22
+ end
23
+
24
+ # Inverse of `can`.
25
+ def cannot(verb, klass=nil, columns=[], &block)
26
+ columns = [columns] unless columns.is_a? Array
27
+ rules << [false, verb, get_class(klass), columns, block]
28
+ end
29
+
30
+ # ---
31
+
32
+ # Checks if the owner can perform `verb` on the given `object` (or class).
33
+ def can?(verb, object=nil, columns=[])
34
+ columns = [columns] unless columns.is_a? Array
35
+ rules = rules_for(verb, get_class(object))
36
+ rules.inject(false) do |bool, (sign, _, _, cols, proc)|
37
+ sign ?
38
+ ((bool || !proc || proc.call(object)) && ((columns & cols) == columns)) : # can
39
+ (bool && proc && !proc.call(object) && (columns.empty? || (columns & cols) != columns)) # cannot
40
+ end
41
+ end
42
+
43
+ # Inverse of `can?`.
44
+ def cannot?(verb, object=nil, columns=[])
45
+ !can?(verb, object, columns)
46
+ end
47
+
48
+ # ---
49
+
50
+ # Ensures that the owner can perform `verb` on `object/class` -- raises an
51
+ # error otherwise.
52
+ def authorize!(verb, object=nil)
53
+ can?(verb, object) or raise Error.new("Access denied (#{verb})")
54
+ end
55
+
56
+ # Inverse of `authorize!`.
57
+ def unauthorize!(verb, object=nil)
58
+ cannot?(verb, object) or raise Error.new("Access denied (#{verb})")
59
+ end
60
+
61
+ # ---
62
+
63
+ # Returns a subset of `rules` that match the given `verb` and `class`.
64
+ def rules_for(verb, klass)
65
+ rules.select do |(sign, _verb, _klass, cols, block)|
66
+ (_verb == :manage || _verb == verb) &&
67
+ (_klass == :all || _klass == klass)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def get_class(object)
74
+ [NilClass, Symbol, Class].include?(object.class) ? object : object.class
75
+ end
76
+ end
77
+
78
+ # Provides `#can?` and `#cannot?` and other helpers.
79
+ # Assumes that you have an `#ability` method defined.
80
+ module AbilityList::Helpers
81
+ def can?(*a)
82
+ abilities && abilities.can?(*a)
83
+ end
84
+
85
+ def cannot?(*a)
86
+ !abilities || abilities.cannot?(*a)
87
+ end
88
+
89
+ def authorize!(*a)
90
+ raise AbilityList::Error.new("No 'ability' defined") unless abilities
91
+ abilities.authorize!(*a)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,48 @@
1
+ require 'wedge/plugins/form'
2
+
3
+ class Wedge
4
+ module Plugins
5
+ class CurrentUser < Form
6
+ plugin :ability_list
7
+
8
+ include AbilityList::Helpers
9
+
10
+ name :current_user_plugin
11
+
12
+ on :compile do |for_client|
13
+ # todo: raise error meaningful error if either of these files don't exist
14
+ %w'ability_list current_user'.each do |type|
15
+ config.compile_str ||= ''
16
+ path = Wedge.config.component_class[:"#{type}"].config.path
17
+ code = File.read("#{Dir.pwd}/#{Wedge.config.app_dir}/#{path}.rb")
18
+ config.compile_str << Opal.original_compile("require 'wedge/plugins/#{type}'; #{code}")
19
+ end if for_client
20
+ end
21
+
22
+ module InstanceMethods
23
+ def wedge_current_user
24
+ @wedge_current_user ||= Wedge[:current_user, wedge(:current_user_plugin).get_current_user]
25
+ end
26
+ alias_method :current_user, :wedge_current_user
27
+ end
28
+
29
+ on :server do
30
+ def get_current_user
31
+ data = instance_exec(&Wedge[:current_user_plugin].config.block) || {}
32
+
33
+ if from_client?
34
+ form = Wedge[:current_user, data]
35
+ client_fields = config.settings[:client_fields]
36
+ form.attributes.select { |k, v| client_fields.include? k }
37
+ else
38
+ data
39
+ end
40
+ end
41
+ end
42
+
43
+ def abilities
44
+ @abilities ||= Wedge[:ability_list, self]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ class Wedge
2
+ module Plugins
3
+ class Factory < Form
4
+ name :factory, :factory_plugin
5
+
6
+ def initialize
7
+ class_store[:stubs] ||= IndifferentHash.new
8
+ end
9
+
10
+ def stub data, name, keys = false
11
+ class_store[:stubs][name] = parse data, keys
12
+ end
13
+
14
+ def [] name, data = {}
15
+ data = IndifferentHash.new(data)
16
+ store_data = IndifferentHash.new class_store[:stubs][name].deep_dup
17
+ HashObject.new store_data.merge data
18
+ end
19
+
20
+ private
21
+
22
+ def parse data, keys = false
23
+ data = data.deep_dup
24
+ parsed_data = data.to_h if data.respond_to? :to_h
25
+ parsed_data = JSON.parse data.to_json
26
+
27
+ keys.each do |k, v|
28
+ d = data.kind_of?(Hash) ? data[k] : data.send(k)
29
+ parsed_data[k] = parse d, v
30
+ end if keys
31
+
32
+ parsed_data
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
- require 'wedge/plugins/validations'
2
- require 'forwardable'
1
+ require_relative 'form/validations'
2
+ require_relative 'render'
3
3
 
4
4
  class Wedge
5
5
  module Plugins
@@ -8,13 +8,16 @@ class Wedge
8
8
 
9
9
  include Methods
10
10
  include Validations
11
+ include Render
11
12
 
13
+ # This allows us to call super
12
14
  module Delegates
13
15
  def _delegates(*names)
14
16
  accessors = Module.new do
15
- extend Forwardable # DISCUSS: do we really need Forwardable here?
17
+ extend Forwardable
18
+
16
19
  names.each do |name|
17
- delegate [name, "#{name}="] => :_attributes
20
+ delegate [name, "#{name}="] => :_atts
18
21
  end
19
22
  end
20
23
  include accessors
@@ -23,420 +26,290 @@ class Wedge
23
26
 
24
27
  extend Delegates
25
28
 
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
29
+ class Atts
30
+ attr_accessor :_atts, :_form
31
+ attr_reader :_options, :_accessors, :_aliases
37
32
 
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
33
+ def initialize atts, accessors, aliases, options
34
+ @_atts = atts.kind_of?(Hash) ? HashObject.new(atts) : atts
35
+ @_accessors = accessors
36
+ @_aliases = aliases
37
+ @_options = options
46
38
 
47
- define_singleton_method attr do
48
- instance_variable_get(:"@#{attr}")
49
- end
50
- end
51
- end
39
+ set_atts
40
+ set_accessors
52
41
 
53
- def _attributes
54
- @_attributes ||= []
42
+ self
55
43
  end
56
44
 
57
- def empty?
58
- _attributes.empty?
59
- end
60
- end
45
+ def set_atts
46
+ atts_hash = {}
61
47
 
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))
48
+ _accessors.each do |att|
49
+ atts_hash[att] = _atts.respond_to?(att) ? _atts.send(att) : nil
109
50
  end
110
- @_attributes.set_values opts
111
51
 
112
- send("#{key}=", opts[key])
52
+ @_atts = HashObject.new atts_hash
113
53
  end
114
- end
115
54
 
116
- def self.attr_accessor(*vars)
117
- @_attr_accessors ||= []
118
- @_form ||= {}
55
+ def set_accessors
56
+ _accessors.each do |att|
57
+ att_options = _options[att]
58
+ alias_att = _aliases[att]
59
+
60
+ define_singleton_method att do
61
+ _atts.send(att) if can_read?(att)
62
+ end
119
63
 
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
64
+ define_singleton_method "#{att}=" do |val, override = false|
65
+ if can_write?(att, override)
66
+ _atts.send("#{att}=", process_value(val, att_options))
67
+ end
68
+ end
125
69
 
126
- unless @_attr_accessors.include? v.first
127
- @_attr_accessors << v.first
128
- @_form[v.first] = v.last
70
+ if alias_att
71
+ define_singleton_method(alias_att) { send(att) }
72
+ define_singleton_method("#{alias_att}=") { |val, override = false| send("#{att}=", val, override) }
129
73
  end
130
74
  end
131
75
  end
132
76
 
133
- _delegates(*_attr_accessors)
134
- end
77
+ def can_read? att
78
+ att_options = _options[att]
135
79
 
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/]
80
+ return true if !att_options[:if] && !att_options[:unless]
81
+ return true if att_options[:if] && _form.instance_exec(&att_options[:if])
82
+ return true if att_options[:unless] && !_form.instance_exec(&att_options[:unless])
142
83
 
143
- super
84
+ false
144
85
  end
145
- end
146
86
 
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
87
+ def can_write? att, override = false
88
+ att_options = _options[att]
153
89
 
154
- att = ivar[1..-1].to_sym
155
- atts[att] = _attributes.send(att)
90
+ override || (can_read?(att) && (!att_options[:read_only]))
91
+ end
156
92
 
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
93
+ def set_defaults _form = self
94
+ @_form = _form
95
+
96
+ _accessors.each do |att|
97
+ att_options = _options[att].deep_dup
98
+ default = att_options[:default]
99
+ default = _form.instance_exec(&default) if default.kind_of? Proc
100
+ default = _form.send("default_#{att}") if _form.respond_to? "default_#{att}"
101
+
102
+ if form = att_options.delete(:form)
103
+ send("#{att}=", Wedge[
104
+ # name
105
+ "#{form}_form",
106
+ # attributes
107
+ (_atts.respond_to?(att) ? (_atts.send(att) || {}) : {}),
108
+ # options
109
+ { _nested: true }.merge(att_options)
110
+ ])
111
+ elsif att_options.key? :default
112
+ send("#{att}=", default, true)
159
113
  end
160
114
  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
115
 
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
116
+ self
182
117
  end
183
118
 
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)
119
+ def process_value val, opts
120
+ # Make sure the value is the correct type
121
+ if type = opts[:type]
122
+ val = case type
123
+ when 'Integer'
124
+ val.to_i
125
+ when 'String'
126
+ val.to_s
127
+ when 'Symbol'
128
+ val.to_sym
129
+ end
192
130
  end
131
+
132
+ val
193
133
  end
194
134
  end
195
135
 
196
- def display_errors options = {}, &block
197
- dom = options.delete(:dom) || _dom
198
- d_errors = errors
136
+ class << self
137
+ attr_accessor :_accessors, :_accessor_options, :_aliases
199
138
 
200
- if override_errors = options[:override_errors]
201
- d_errors = override_errors
202
- end
139
+ alias_method :original_attr_reader, :attr_reader
140
+ def attr_reader(*attrs, &block)
141
+ default_opts = { read_only: true }
142
+ opts = attrs.pop
143
+ opts.merge!(default_opts) if opts.is_a? Hash
203
144
 
204
- keys = options.delete(:keys) || (_options[:key] ? [_options[:key]] : [])
145
+ attrs << opts
205
146
 
206
- if extra_errors = options.delete(:errors)
207
- extra_errors.each do |key, value|
208
- d_errors[key] = value
209
- end
147
+ attr_accessor(*attrs, &block)
210
148
  end
211
149
 
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
150
+ def form_accessor name, options = {}
151
+ attr_accessor *[name, { form: name }.merge(options)]
152
+ end
227
153
 
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)
154
+ alias_method :original_attr_accessor, :attr_accessor
155
+ def attr_accessor(*attrs, &block)
156
+ attrs.each_with_index do |att, i|
157
+ if att.is_a? Hash
158
+ # remove the hash from the attrs, use them as options
159
+ options = attrs.delete_at i
160
+ # set the type class to aa string so it's not turned into an
161
+ # anonymous class
162
+ if type = options.delete(:type)
163
+ options[:type] = type.to_s
164
+ end
165
+ # merge and att them to the accessor options
166
+ attrs.each do |a|
167
+ ((@_accessor_options ||= IndifferentHash.new)[a] ||= {}).merge! options
233
168
  end
234
169
  else
235
- field_error_dom = DOM.new('<span class="field-error"><span>')
170
+ # issue: OPAL is not using the alias method original_attr_reader
171
+ # correctly. It's still somehow getting in here when called below.
172
+ next if %w'_atts _options'.include? att.to_s
173
+ ###################################################################
174
+
175
+ # set empty options if need be
176
+ (@_accessor_options ||= IndifferentHash.new)[att] ||= {}
177
+ # store the accessors
178
+ ((@_accessors ||= []) << att).uniq!
179
+ define_method(att) { _atts.send att }
236
180
  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
181
  end
182
+
183
+ _delegates(*attrs)
243
184
  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
185
 
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
186
+ # We need to set instance variables on the inherited class
187
+ def inherited(subclass)
188
+ return if name == 'Wedge::Plugins::Form'
189
+
190
+ subclass.instance_variable_set :@_accessors, @_accessors.deep_dup
191
+ subclass.instance_variable_set :@_accessor_options, @_accessor_options.deep_dup
285
192
  end
286
- end
287
193
 
288
- def _attributes
289
- @_attributes ||= {}
194
+ def model_alias alias_name, original_name
195
+ @_aliases ||= IndifferentHash.new
196
+ @_aliases[original_name] = alias_name
197
+ # discuss: should we also alias_method. right now I'm think no, reason
198
+ # being it's just a model alias and shouldn't allow people to call
199
+ # that method on the form to avoid some people using one name and some
200
+ # another.
201
+ # alias_method alias_name, original_name
202
+ end
203
+ alias alias_model model_alias
290
204
  end
291
205
 
292
- def validate_msg error, column
293
- false
294
- end
206
+ original_attr_reader :_atts, :_options
295
207
 
296
- protected
208
+ # Initialize with a hash of attributes and values.
209
+ # Extra attributes are discarded.
210
+ #
211
+ # @example
212
+ #
213
+ # class EditPost < Scrivener
214
+ # attr_accessor :title
215
+ # attr_accessor :body
216
+ #
217
+ # def validate
218
+ # assert_present :title
219
+ # assert_present :body
220
+ # end
221
+ # end
222
+ #
223
+ # edit = EditPost.new(title: "Software Tools")
224
+ #
225
+ # edit.valid? #=> false
226
+ #
227
+ # edit.errors[:title] #=> []
228
+ # edit.errors[:body] #=> [:not_present]
229
+ #
230
+ # edit.body = "Recommended reading..."
231
+ #
232
+ # edit.valid? #=> true
233
+ #
234
+ # # Now it's safe to initialize the model.
235
+ # post = Post.new(edit.attributes)
236
+ # post.save
237
+ def initialize(atts = {}, options = {})
238
+ atts = atts.deep_dup
239
+ @_options = options.indifferent
240
+ @_atts = Atts.new atts, _accessors, _aliases, _accessor_options
241
+ @_atts = @_atts.set_defaults self
297
242
 
298
- def _data
299
- @_data ||= {}
300
- end
243
+ atts.each do |key, val|
244
+ # grab the original key if alias is given
245
+ key = _aliases.invert[key] || key
301
246
 
302
- def self._attr_accessors
303
- @_attr_accessors ||= []
304
- end
247
+ next if (_accessor_options[key] || {})[:form]
305
248
 
306
- def self._form
307
- @_form || {}
308
- end
249
+ accessor = "#{key}="
309
250
 
310
- def _form
311
- self.class._form
251
+ if respond_to?(accessor)
252
+ send(accessor, val)
253
+ end
254
+ end
312
255
  end
313
256
 
314
- def _attr_accessors
315
- self.class._attr_accessors
257
+ def _accessors
258
+ @_accessors ||= (self.class._accessors || IndifferentHash.new).deep_dup
316
259
  end
317
260
 
318
- def _options
319
- @_options
261
+ def _accessor_options
262
+ @_accessor_options ||= (self.class._accessor_options || IndifferentHash.new).deep_dup
320
263
  end
321
264
 
322
- def _dom
323
- @_dom ||= @_options[:dom]
265
+ def _aliases
266
+ @_aliases || (self.class._aliases || IndifferentHash.new).deep_dup
324
267
  end
325
268
 
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
269
+ def nested?
270
+ @_options[:_nested] ? true : false
337
271
  end
338
272
 
339
- def empty?
340
- _attributes.empty?
273
+ def attributes?
274
+ @_options[:_attributes] ? true : false
341
275
  end
342
276
 
343
- def wedge_config
344
- @wedge_config ||= begin
345
- c = super
346
- c.skip_method_wrap
347
- c
348
- end
277
+ def model_attributes?
278
+ @_options[:_model_attributes] ? true : false
349
279
  end
350
280
 
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
281
+ # Return hash of attributes and values.
282
+ def attributes for_model = false
283
+ IndifferentHash.new.tap do |atts|
284
+ _options[:_attributes] = true
285
+ _options[:_model_attributes] = for_model
286
+
287
+ _accessors.each do |att|
288
+ opts = _accessor_options[att]
289
+ if _atts.can_read?(att) && (!opts[:hidden] || opts[:hidden].is_a?(Proc) && !self.instance_exec(&opts[:hidden]))
290
+ is_form = opts[:form]
291
+ key = for_model ? _aliases[att] || att : att
292
+ key = (for_model && is_form)? "#{key}_attributes" : key
293
+ atts[key] = is_form ? send(att).send(for_model ? 'model_attributes' : 'attributes') : send(att)
393
294
  end
394
295
  end
395
-
396
- l_dom
397
296
  end
297
+ end
398
298
 
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
299
+ def model_attributes
300
+ attributes true
301
+ end
411
302
 
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
303
+ alias atts _atts
304
+ alias options _options
425
305
 
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
306
+ def slice(*keys)
307
+ IndifferentHash.new.tap do |atts|
308
+ keys.each do |att|
309
+ atts[att] = send(att)
433
310
  end
434
-
435
- value
436
311
  end
437
312
  end
438
313
  end
439
314
  end
440
315
  end
441
-
442
- Wedge::Form = Wedge::Plugins::Form