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