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.
- checksums.yaml +4 -4
- data/Gemfile +8 -1
- data/Makefile +2 -0
- data/Rakefile +8 -4
- data/TODO.md +4 -0
- data/lib/roda/plugins/wedge.rb +14 -96
- data/lib/wedge.rb +9 -4
- data/lib/wedge/component.rb +70 -29
- data/lib/wedge/config.rb +36 -6
- data/lib/wedge/middleware.rb +43 -35
- data/lib/wedge/opal.rb +15 -4
- data/lib/wedge/plugins/ability_list.rb +95 -0
- data/lib/wedge/plugins/current_user.rb +48 -0
- data/lib/wedge/plugins/factory.rb +36 -0
- data/lib/wedge/plugins/form.rb +216 -343
- data/lib/wedge/plugins/{validations.rb → form/validations.rb} +64 -37
- data/lib/wedge/plugins/form_backup.rb +442 -0
- data/lib/wedge/plugins/render.rb +110 -0
- data/lib/wedge/plugins/uploader.rb +2 -2
- data/lib/wedge/utilis/duplicable.rb +104 -0
- data/lib/wedge/utilis/hash.rb +48 -0
- data/lib/wedge/version.rb +1 -1
- data/playground/app/app.rb +27 -11
- data/playground/app/components/abilities.rb +11 -0
- data/playground/app/components/current_user.rb +12 -0
- data/playground/app/components/layout.rb +5 -0
- data/playground/app/components/todo_list.rb +24 -0
- data/playground/app/config/boot.rb +3 -0
- data/playground/app/forms/todo_list_add.rb +9 -0
- data/playground/app/models/user.rb +5 -0
- data/playground/public/css/styles.css +139 -0
- data/playground/public/css/todo_list.css +138 -0
- data/playground/public/todo_list.html +23 -0
- data/playground/src/css/styles.scss +2 -1
- data/playground/src/css/todo_list.scss +165 -0
- data/playground/src/todo_list.slim +17 -0
- data/spec/playground/uploader_spec.rb +27 -29
- data/spec/spec_helper.rb +29 -0
- data/spec/stubs/models/user.rb +15 -0
- data/spec/wedge/plugins/current_user_spec.rb +29 -0
- data/spec/wedge/plugins/factory_spec.rb +16 -0
- data/spec/wedge/plugins/form_spec.rb +119 -0
- data/spec/wedge/plugins/uploader_spec.rb +1 -3
- data/spec/wedge_spec.rb +3 -3
- metadata +28 -3
data/lib/wedge/middleware.rb
CHANGED
@@ -1,24 +1,47 @@
|
|
1
1
|
class Wedge
|
2
2
|
class Middleware
|
3
|
-
def initialize(app, settings = {})
|
4
|
-
@app
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
Wedge.config.
|
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
|
-
|
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
|
62
|
-
res
|
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!(
|
80
|
+
res = Wedge.scope!(scope)[name].send(method_called, *method_args) || ''
|
68
81
|
end
|
69
82
|
|
70
|
-
#
|
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
|
-
|
82
|
-
|
83
|
-
|
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.
|
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(
|
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
|
data/lib/wedge/plugins/form.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
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
|
17
|
+
extend Forwardable
|
18
|
+
|
16
19
|
names.each do |name|
|
17
|
-
delegate [name, "#{name}="] => :
|
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
|
27
|
-
|
28
|
-
|
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
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
39
|
+
set_atts
|
40
|
+
set_accessors
|
52
41
|
|
53
|
-
|
54
|
-
@_attributes ||= []
|
42
|
+
self
|
55
43
|
end
|
56
44
|
|
57
|
-
def
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
45
|
+
def set_atts
|
46
|
+
atts_hash = {}
|
61
47
|
|
62
|
-
|
63
|
-
|
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
|
-
|
52
|
+
@_atts = HashObject.new atts_hash
|
113
53
|
end
|
114
|
-
end
|
115
54
|
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
134
|
-
|
77
|
+
def can_read? att
|
78
|
+
att_options = _options[att]
|
135
79
|
|
136
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
84
|
+
false
|
144
85
|
end
|
145
|
-
end
|
146
86
|
|
147
|
-
|
148
|
-
|
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
|
-
|
155
|
-
|
90
|
+
override || (can_read?(att) && (!att_options[:read_only]))
|
91
|
+
end
|
156
92
|
|
157
|
-
|
158
|
-
|
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
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
197
|
-
|
198
|
-
d_errors = errors
|
136
|
+
class << self
|
137
|
+
attr_accessor :_accessors, :_accessor_options, :_aliases
|
199
138
|
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
145
|
+
attrs << opts
|
205
146
|
|
206
|
-
|
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
|
-
|
213
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
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
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
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
|
-
|
289
|
-
|
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
|
-
|
293
|
-
false
|
294
|
-
end
|
206
|
+
original_attr_reader :_atts, :_options
|
295
207
|
|
296
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
243
|
+
atts.each do |key, val|
|
244
|
+
# grab the original key if alias is given
|
245
|
+
key = _aliases.invert[key] || key
|
301
246
|
|
302
|
-
|
303
|
-
@_attr_accessors ||= []
|
304
|
-
end
|
247
|
+
next if (_accessor_options[key] || {})[:form]
|
305
248
|
|
306
|
-
|
307
|
-
@_form || {}
|
308
|
-
end
|
249
|
+
accessor = "#{key}="
|
309
250
|
|
310
|
-
|
311
|
-
|
251
|
+
if respond_to?(accessor)
|
252
|
+
send(accessor, val)
|
253
|
+
end
|
254
|
+
end
|
312
255
|
end
|
313
256
|
|
314
|
-
def
|
315
|
-
self.class.
|
257
|
+
def _accessors
|
258
|
+
@_accessors ||= (self.class._accessors || IndifferentHash.new).deep_dup
|
316
259
|
end
|
317
260
|
|
318
|
-
def
|
319
|
-
@
|
261
|
+
def _accessor_options
|
262
|
+
@_accessor_options ||= (self.class._accessor_options || IndifferentHash.new).deep_dup
|
320
263
|
end
|
321
264
|
|
322
|
-
def
|
323
|
-
@
|
265
|
+
def _aliases
|
266
|
+
@_aliases || (self.class._aliases || IndifferentHash.new).deep_dup
|
324
267
|
end
|
325
268
|
|
326
|
-
def
|
327
|
-
|
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
|
340
|
-
_attributes
|
273
|
+
def attributes?
|
274
|
+
@_options[:_attributes] ? true : false
|
341
275
|
end
|
342
276
|
|
343
|
-
def
|
344
|
-
@
|
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
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
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
|
-
|
400
|
-
|
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
|
-
|
413
|
-
|
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
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
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
|