wedge 0.1.17 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- 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
|