kiss 1.1 → 1.7
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.
- data/LICENSE +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/kiss +151 -34
- data/data/scaffold.tgz +0 -0
- data/lib/kiss.rb +389 -742
- data/lib/kiss/accessors/controller.rb +47 -0
- data/lib/kiss/accessors/request.rb +106 -0
- data/lib/kiss/accessors/template.rb +23 -0
- data/lib/kiss/action.rb +502 -132
- data/lib/kiss/bench.rb +14 -5
- data/lib/kiss/debug.rb +14 -6
- data/lib/kiss/exception_report.rb +22 -299
- data/lib/kiss/ext/core.rb +700 -0
- data/lib/kiss/ext/rack.rb +33 -0
- data/lib/kiss/ext/sequel_database.rb +47 -0
- data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
- data/lib/kiss/form.rb +404 -179
- data/lib/kiss/form/field.rb +183 -307
- data/lib/kiss/form/field_types.rb +239 -0
- data/lib/kiss/format.rb +88 -70
- data/lib/kiss/html/exception_report.css +222 -0
- data/lib/kiss/html/exception_report.html +210 -0
- data/lib/kiss/iterator.rb +14 -12
- data/lib/kiss/login.rb +8 -8
- data/lib/kiss/mailer.rb +68 -66
- data/lib/kiss/model.rb +323 -36
- data/lib/kiss/rack/bench.rb +16 -8
- data/lib/kiss/rack/email_errors.rb +25 -15
- data/lib/kiss/rack/errors_ok.rb +2 -2
- data/lib/kiss/rack/facebook.rb +6 -6
- data/lib/kiss/rack/file_not_found.rb +10 -8
- data/lib/kiss/rack/log_exceptions.rb +3 -3
- data/lib/kiss/rack/recorder.rb +2 -2
- data/lib/kiss/rack/show_debug.rb +2 -2
- data/lib/kiss/rack/show_exceptions.rb +2 -2
- data/lib/kiss/request.rb +435 -0
- data/lib/kiss/sequel_session.rb +15 -14
- data/lib/kiss/static_file.rb +20 -13
- data/lib/kiss/template.rb +327 -0
- metadata +60 -25
- data/lib/kiss/controller_accessors.rb +0 -81
- data/lib/kiss/hacks.rb +0 -188
- data/lib/kiss/sequel_mysql.rb +0 -25
- data/lib/kiss/template_methods.rb +0 -167
@@ -0,0 +1,47 @@
|
|
1
|
+
class Kiss
|
2
|
+
module KissAccessors
|
3
|
+
# Escapes string for use in URLs.
|
4
|
+
def url_escape(string)
|
5
|
+
string.url_escape
|
6
|
+
end
|
7
|
+
alias_method :u, :url_escape
|
8
|
+
alias_method :escape, :url_escape
|
9
|
+
alias_method :url_encode, :url_escape
|
10
|
+
alias_method :escape_url, :url_escape
|
11
|
+
|
12
|
+
# Encodes string for output to HTML.
|
13
|
+
def html_escape(string)
|
14
|
+
string.html_escape
|
15
|
+
end
|
16
|
+
alias_method :h, :html_escape
|
17
|
+
alias_method :escape_html, :html_escape
|
18
|
+
end
|
19
|
+
|
20
|
+
module ControllerAccessors
|
21
|
+
def environment
|
22
|
+
controller.environment
|
23
|
+
end
|
24
|
+
|
25
|
+
def public_dir
|
26
|
+
controller.public_dir
|
27
|
+
end
|
28
|
+
|
29
|
+
def upload_dir
|
30
|
+
controller.upload_dir
|
31
|
+
end
|
32
|
+
|
33
|
+
def database
|
34
|
+
controller.database
|
35
|
+
end
|
36
|
+
|
37
|
+
def models
|
38
|
+
controller.models
|
39
|
+
end
|
40
|
+
alias_method :dbm, :models
|
41
|
+
|
42
|
+
|
43
|
+
# request data
|
44
|
+
|
45
|
+
include KissAccessors
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Kiss
|
2
|
+
module RequestAccessors
|
3
|
+
def models
|
4
|
+
request.models
|
5
|
+
end
|
6
|
+
alias_method :dbm, :models
|
7
|
+
|
8
|
+
def file_cache(*args, &block)
|
9
|
+
request.file_cache(*args, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def session
|
13
|
+
request.session
|
14
|
+
end
|
15
|
+
|
16
|
+
def login
|
17
|
+
request.login
|
18
|
+
end
|
19
|
+
|
20
|
+
def redirect_url(*args)
|
21
|
+
request.redirect_url(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def redirect_action(*args)
|
25
|
+
request.redirect_action(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def app(*args)
|
29
|
+
request.app_url(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pub(*args)
|
33
|
+
request.pub_url(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_file(*args)
|
37
|
+
request.send_file(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_response(*args)
|
41
|
+
request.send_response(*args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def env
|
45
|
+
request.env
|
46
|
+
end
|
47
|
+
|
48
|
+
def protocol
|
49
|
+
request.protocol
|
50
|
+
end
|
51
|
+
|
52
|
+
def host
|
53
|
+
request.host
|
54
|
+
end
|
55
|
+
|
56
|
+
def path_info
|
57
|
+
request.path_info
|
58
|
+
end
|
59
|
+
|
60
|
+
def query_string_with_params
|
61
|
+
request.query_string_with_params
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_url_with_params
|
65
|
+
request.request_url_with_params
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_exception_cache(*args)
|
69
|
+
request.set_exception_cache(*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
def debug(object)
|
73
|
+
request.debug(object, Kernel.caller[0])
|
74
|
+
end
|
75
|
+
alias_method :trace, :debug
|
76
|
+
|
77
|
+
def start_benchmark(label = nil)
|
78
|
+
request.start_benchmark(label, Kernel.caller[0])
|
79
|
+
end
|
80
|
+
alias_method :bench, :start_benchmark
|
81
|
+
|
82
|
+
def stop_benchmark
|
83
|
+
request.stop_benchmark(Kernel.caller[0])
|
84
|
+
end
|
85
|
+
alias_method :bench_stop, :stop_benchmark
|
86
|
+
|
87
|
+
def response
|
88
|
+
request.response
|
89
|
+
end
|
90
|
+
|
91
|
+
def cookies
|
92
|
+
request.cookies
|
93
|
+
end
|
94
|
+
|
95
|
+
def set_cookie(*args)
|
96
|
+
response.set_cookie(*args)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module DatabaseAccessors
|
101
|
+
def database
|
102
|
+
request.database
|
103
|
+
end
|
104
|
+
alias_method :db, :database
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Kiss
|
2
|
+
module TemplateMethods
|
3
|
+
# Contains data set by action/mailer logic to be displayed in templates.
|
4
|
+
def data
|
5
|
+
@data
|
6
|
+
end
|
7
|
+
alias_method :vars, :data
|
8
|
+
|
9
|
+
def data=(hash)
|
10
|
+
@data = hash
|
11
|
+
end
|
12
|
+
alias_method :'vars=', :'data='
|
13
|
+
|
14
|
+
# Merges specified data (key-value pairs) into template data hash
|
15
|
+
# (provided for backward-compatibility with apps pre-1.2).
|
16
|
+
def set(vars)
|
17
|
+
vars.each_pair do |key, value|
|
18
|
+
@data[key] = value
|
19
|
+
self.instance_variable_set(:"@#{key}", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/kiss/action.rb
CHANGED
@@ -1,78 +1,327 @@
|
|
1
1
|
class Kiss
|
2
2
|
class Action
|
3
|
+
@@template_class = nil
|
4
|
+
|
5
|
+
cattr_reader :class_unique, :template_class
|
6
|
+
|
3
7
|
class << self
|
8
|
+
_attr_reader :subdir, :part, :name, :action_path, :parent_class, :object_options, :aliases
|
9
|
+
attr_reader :layout, :breadcrumbs, :authentication_required, :base_url
|
10
|
+
|
11
|
+
dsl_accessor :breadcrumb, :object_breadcrumb
|
12
|
+
|
4
13
|
def inherited(subclass)
|
5
|
-
subclass.
|
14
|
+
subclass.init_child_classes
|
6
15
|
end
|
7
|
-
def
|
8
|
-
@
|
16
|
+
def init_child_classes
|
17
|
+
@_child_classes = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_subclass_from_path(path, request, params)
|
21
|
+
action_subdir = ''
|
22
|
+
action_path = '/'
|
23
|
+
action_found = false
|
24
|
+
args = []
|
25
|
+
objects = {}
|
26
|
+
breadcrumbs = []
|
27
|
+
object = nil
|
28
|
+
object_part = nil
|
29
|
+
collection = nil
|
30
|
+
|
31
|
+
request.redirect_action('/') if path == ''
|
32
|
+
|
33
|
+
action_class = self.get_child_class
|
34
|
+
|
35
|
+
parts = path.sub(/\A\/*/, '').split(/\/+/)
|
36
|
+
parts.push('') if path[-1, 1] == '/'
|
37
|
+
|
38
|
+
while part = parts.shift
|
39
|
+
action_path += part
|
40
|
+
part = (object_part ? 'view' : @@controller.default_action) if part.empty?
|
41
|
+
|
42
|
+
# =column syntax not yet supported
|
43
|
+
# when adding this support, be sure to change Action#redirect_action also
|
44
|
+
|
45
|
+
if part =~ /\A([\w\-\.\+\%]+)\=([\w\-\.\+\%]+)\Z/
|
46
|
+
params[$1.url_unescape] = $2.url_unescape
|
47
|
+
action_path += '/'
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
if part !~ /\A[\w\-\.]*\Z/i
|
52
|
+
raise Kiss::FileNotFoundError::InvalidAction, 'invalid action path'
|
53
|
+
end
|
54
|
+
|
55
|
+
if redirect = action_class.aliases[part]
|
56
|
+
action, options = redirect
|
57
|
+
request.redirect_action(
|
58
|
+
absolute_uri( action + '/' + parts.join('/'), action_path ),
|
59
|
+
options
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
test_path = @@controller.action_dir + action_subdir + '/' + part
|
64
|
+
if @@controller.directory_exists?(test_path)
|
65
|
+
action_subdir += '/' + part
|
66
|
+
action_class = action_class.get_child_class(part, true)
|
67
|
+
object = object_part = nil
|
68
|
+
|
69
|
+
collection = action_class.breadcrumb
|
70
|
+
|
71
|
+
action_path += '/'
|
72
|
+
if action_class.breadcrumbs.empty?
|
73
|
+
if File.file?("#{@@controller.action_dir}#{action_class.name}index.rb") ||
|
74
|
+
File.file?("#{@@controller.action_dir}#{action_class.name}index.rhtml")
|
75
|
+
breadcrumbs << [action_class.breadcrumb, action_path]
|
76
|
+
end
|
77
|
+
else
|
78
|
+
breadcrumbs.push(*action_class.breadcrumbs)
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# remove .rb extension, if any
|
82
|
+
part.sub!(/(\.rb)+\Z/, '')
|
83
|
+
|
84
|
+
# does part have an extension?
|
85
|
+
if part =~ /\A(.+)\.(\w+)\Z/
|
86
|
+
extension = $2
|
87
|
+
part = $1
|
88
|
+
else
|
89
|
+
extension = 'rhtml'
|
90
|
+
end
|
91
|
+
|
92
|
+
test_path = @@controller.action_dir + action_subdir + '/' + part
|
93
|
+
if File.file?("#{test_path}.rb") || File.file?("#{test_path}.#{(extension == 'xls') ? 'txt' : extension}")
|
94
|
+
action_found = true
|
95
|
+
action_class = action_class.get_child_class(part)
|
96
|
+
break
|
97
|
+
else
|
98
|
+
unless part =~ /\A[\w\.\-]+\Z/
|
99
|
+
raise Kiss::FileNotFoundError, "invalid URI"
|
100
|
+
end
|
101
|
+
|
102
|
+
object_options = action_class.object_options
|
103
|
+
if object_options && (object_options[:column] != :id || part =~ /\A\d+\Z/)
|
104
|
+
object_part = part
|
105
|
+
object = request.dbm[object_options[:class_name]].find(object_options[:column] => object_part)
|
106
|
+
objects[object_options[:variable_name]] = object
|
107
|
+
|
108
|
+
action_path += '/'
|
109
|
+
|
110
|
+
if !object
|
111
|
+
raise Kiss::FileNotFoundError, "resource '#{test_path.html_escape}' not found"
|
112
|
+
end
|
113
|
+
|
114
|
+
object_breadcrumb = action_class.object_breadcrumb
|
115
|
+
object_display_name = object[object.class.display_column] ||
|
116
|
+
(object.send(object.class.display_column) rescue nil)
|
117
|
+
if (object_breadcrumb != false)
|
118
|
+
breadcrumbs << [
|
119
|
+
(object_breadcrumb.is_a?(Proc) ? object_breadcrumb.call(object) : object_breadcrumb) ||
|
120
|
+
object_display_name,
|
121
|
+
action_path
|
122
|
+
]
|
123
|
+
end
|
124
|
+
|
125
|
+
next
|
126
|
+
else
|
127
|
+
if part == 'view'
|
128
|
+
request.redirect_action(path + '..')
|
129
|
+
end
|
130
|
+
raise Kiss::FileNotFoundError, "resource '#{test_path.html_escape}' not found"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# if no action found, add a trailing slash and try again
|
137
|
+
request.redirect_action(path + '/') unless action_found
|
138
|
+
|
139
|
+
# keep rest of path_info in args
|
140
|
+
args.push(*parts)
|
141
|
+
|
142
|
+
action = action_class.new(request, action_path, extension, collection, object, object_part, object_display_name, breadcrumbs, args, params)
|
143
|
+
objects.each_pair do |key, value|
|
144
|
+
action.instance_variable_set(key, value)
|
145
|
+
end
|
146
|
+
action
|
9
147
|
end
|
10
148
|
|
11
|
-
def
|
149
|
+
def get_root_class(controller, root_dir)
|
150
|
+
parent = self
|
151
|
+
klass = Class.new(self)
|
152
|
+
Kiss.register_class_path(klass, root_dir)
|
153
|
+
klass.class_eval do
|
154
|
+
@@action_dir = root_dir
|
155
|
+
@@controller = controller
|
156
|
+
@@root_class = klass
|
157
|
+
|
158
|
+
@_parent_class = parent
|
159
|
+
@_subdir = ''
|
160
|
+
|
161
|
+
@layout = nil
|
162
|
+
@breadcrumbs = []
|
163
|
+
@authentication_required = @@controller.authenticate_all ? true : false
|
164
|
+
|
165
|
+
@_aliases = {}
|
166
|
+
end
|
167
|
+
klass
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_child_class(part = nil, is_directory = !part)
|
12
171
|
# each subdir must get a subclass of the parent dir class;
|
13
|
-
# each subclass has its own @
|
172
|
+
# each subclass has its own @_child_classes hash of its subdirs
|
173
|
+
|
174
|
+
subdir = self.name.to_s.sub(/\/\Z/, '')
|
175
|
+
name = self.name.to_s + part.to_s + (is_directory ? '/' : '')
|
176
|
+
path = name + (is_directory ? '_action' : '') + '.rb'
|
177
|
+
parent = self
|
178
|
+
|
179
|
+
layout = @layout
|
14
180
|
|
15
|
-
|
181
|
+
auth_path = name
|
182
|
+
auth_req = @authentication_required && !@@controller.authenticate_exclude.include?(auth_path)
|
16
183
|
|
17
|
-
|
184
|
+
src, updated = @@controller.file_cache(@@action_dir + path, true)
|
185
|
+
|
186
|
+
if updated || !@_child_classes.has_key?(part)
|
18
187
|
klass = Class.new(self)
|
19
|
-
|
20
|
-
|
188
|
+
Kiss.register_class_path(klass, @@action_dir + path)
|
189
|
+
klass.class_eval do
|
190
|
+
@_subdir = subdir
|
191
|
+
@_part = part
|
192
|
+
@_name = name
|
193
|
+
@_action_path = path
|
194
|
+
@_parent_class = parent
|
195
|
+
@_breadcrumb = part ? part.titleize : nil
|
196
|
+
|
197
|
+
@layout = layout
|
198
|
+
@breadcrumbs = []
|
199
|
+
@authentication_required = auth_req
|
200
|
+
|
201
|
+
@_aliases = {}
|
202
|
+
|
203
|
+
selects_object if part && is_directory
|
204
|
+
end
|
205
|
+
|
206
|
+
if src
|
207
|
+
klass.class_eval(src, @@action_dir + path)
|
208
|
+
end
|
209
|
+
@_child_classes[part] = klass
|
21
210
|
end
|
22
211
|
|
23
|
-
@
|
212
|
+
@_child_classes[part]
|
213
|
+
end
|
214
|
+
|
215
|
+
def selects_object(options = {})
|
216
|
+
if options.has_key?(:variable_name)
|
217
|
+
variable_name = options[:variable_name]
|
218
|
+
raise 'variable name must be a symbol' unless variable_name.is_a?(Symbol)
|
219
|
+
raise 'variable name must start with @' unless variable_name.to_s[0, 1] == '@'
|
220
|
+
end
|
221
|
+
|
222
|
+
(@_object_options ||= {
|
223
|
+
:class_name => @_part.pluralize.to_sym,
|
224
|
+
:variable_name => ('@'+@_part.singularize).to_sym,
|
225
|
+
:column => :id
|
226
|
+
}).merge!(options)
|
227
|
+
end
|
228
|
+
alias_method :select_object, :selects_object
|
229
|
+
alias_method :accepts_object, :selects_object
|
230
|
+
alias_method :accept_object, :selects_object
|
231
|
+
|
232
|
+
def alias_action(part, action, options = {})
|
233
|
+
@_aliases[part.to_s] = [action.to_s, options]
|
234
|
+
end
|
235
|
+
|
236
|
+
def absolute_uri(path, action_path)
|
237
|
+
# need to add something to action_path before File.dirname, in case
|
238
|
+
# action_path ends in /
|
239
|
+
path =~ /\A\// ? path : action_path.gsub(/[^\/]*\Z/, '') + path
|
24
240
|
end
|
25
241
|
end
|
26
|
-
self.
|
27
|
-
|
242
|
+
self.init_child_classes
|
28
243
|
|
244
|
+
include Kiss::ControllerAccessors
|
245
|
+
include Kiss::RequestAccessors
|
246
|
+
include Kiss::DatabaseAccessors
|
29
247
|
include Kiss::TemplateMethods
|
30
248
|
|
31
|
-
|
32
|
-
|
249
|
+
attr_accessor :layout, :title
|
250
|
+
|
251
|
+
_attr_reader :request, :controller, :forms, :action, :action_path, :template,
|
252
|
+
:extension, :args, :params, :output, :output_options
|
33
253
|
|
34
254
|
def arg(index)
|
35
|
-
@
|
255
|
+
@_args[index]
|
36
256
|
end
|
37
257
|
|
38
258
|
def args
|
39
|
-
@
|
259
|
+
@_args
|
40
260
|
end
|
41
261
|
|
42
262
|
# Raises custom error message for missing methods, specifying current action
|
43
263
|
# more clearly than standard error message.
|
44
|
-
def method_missing(
|
45
|
-
|
264
|
+
def method_missing(method, *args, &block)
|
265
|
+
if method.to_s =~ /\A_class_(\d+)_(.*)/
|
266
|
+
__send__ $2.to_sym, $1.to_i, *args, &block
|
267
|
+
else
|
268
|
+
raise NoMethodError, "undefined method `#{method}'"
|
269
|
+
end
|
46
270
|
end
|
47
271
|
|
48
272
|
# Creates a new action instance from controller data.
|
49
|
-
def initialize(
|
50
|
-
@
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@
|
54
|
-
@
|
55
|
-
@
|
56
|
-
@
|
273
|
+
def initialize(request, action_path, extension, collection, object, object_part, object_display_name, breadcrumbs, args, params = {})
|
274
|
+
@_request = request
|
275
|
+
@_controller = request.controller
|
276
|
+
@_action_path = action_path
|
277
|
+
@_extension = extension
|
278
|
+
@_object = object
|
279
|
+
@_object_part = object_part
|
280
|
+
@_args = args
|
281
|
+
@_params = params
|
282
|
+
|
283
|
+
@_template = self.class.name
|
284
|
+
|
285
|
+
@_arg_index = -1
|
286
|
+
@_forms = {}
|
57
287
|
|
58
288
|
@data = {}
|
59
|
-
|
60
|
-
@
|
289
|
+
|
290
|
+
@breadcrumbs = breadcrumbs
|
291
|
+
@title =
|
292
|
+
is_index? ? (@breadcrumbs.pop.first unless @breadcrumbs.empty?) :
|
293
|
+
is_view? ? (@breadcrumbs.pop; object_display_name) :
|
294
|
+
is_edit? ? "#{@_object ? 'Edit' : 'Add'} #{collection.first.singularize}" :
|
295
|
+
is_delete? ? "Delete #{collection.first.singularize}" : self.class.part.titlecase
|
296
|
+
|
297
|
+
@base_url = app + action_path
|
298
|
+
|
299
|
+
after_initialize
|
300
|
+
end
|
301
|
+
|
302
|
+
def breadcrumbs_html
|
303
|
+
@breadcrumbs.map do |crumb|
|
304
|
+
%Q(<a href="#{app + crumb[1]}">#{crumb[0]}</a> >)
|
305
|
+
end.join(' ')
|
306
|
+
end
|
307
|
+
|
308
|
+
def context_class
|
309
|
+
Kiss.context_class
|
61
310
|
end
|
62
311
|
|
63
312
|
# Returns true is `form' param matches current action path.
|
64
313
|
def form_action_match
|
65
|
-
|
66
|
-
return false unless form =~ /^\/?\w+(\/\w+)*$/s
|
67
|
-
return form == @action || @action =~ /\/#{form}/
|
314
|
+
return params['form'] == action_path
|
68
315
|
end
|
69
316
|
|
70
317
|
# Raises error if database model object does not match the key=value data in
|
71
318
|
# desired_values hash.
|
72
|
-
def check_desired_values(object,desired_values)
|
73
|
-
desired_values
|
74
|
-
|
75
|
-
|
319
|
+
def check_desired_values(object, desired_values = nil)
|
320
|
+
if desired_values
|
321
|
+
desired_values.each_pair do |key, value|
|
322
|
+
unless object[key] == value
|
323
|
+
raise "#{object.class.table.to_s.singularize} id=#{object.id} does not have #{key} == #{value.inspect}"
|
324
|
+
end
|
76
325
|
end
|
77
326
|
end
|
78
327
|
object
|
@@ -80,90 +329,160 @@ class Kiss
|
|
80
329
|
|
81
330
|
# Verifies valid id value, selects database model object from specified table
|
82
331
|
# with specified id, and checks result to match desired_values.
|
83
|
-
def get_object_by_id(table, id, desired_values =
|
332
|
+
def get_object_by_id(table, id, desired_values = nil)
|
84
333
|
id = id.to_i
|
85
|
-
|
86
|
-
|
87
|
-
|
334
|
+
result = (id > 0) ? dbm[table][id] : nil
|
335
|
+
raise "'#{table}' object not found" unless result
|
336
|
+
|
337
|
+
check_desired_values(result, desired_values)
|
88
338
|
end
|
89
339
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
340
|
+
def get_object_by_value(table, value, column, *args)
|
341
|
+
desired_values = args.first
|
342
|
+
if column
|
343
|
+
result = dbm[table].where(column => value).first
|
344
|
+
raise "'#{table}' object not found" unless result
|
345
|
+
|
346
|
+
check_desired_values(result, desired_values)
|
347
|
+
else
|
348
|
+
get_object_by_id(table, value, desired_values)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns database model object from specified table, with column matching
|
353
|
+
# argument of specified index (defaults to first argument). Raises error if
|
354
|
+
# object not found in database or object data does not match desired_values.
|
355
|
+
def arg_object(table, *args)
|
356
|
+
index = args.first.is_a?(Numeric) ? args.shift : 0
|
357
|
+
value = arg(index)
|
358
|
+
|
359
|
+
column = args.first.is_a?(Symbol) ? args.shift : nil
|
360
|
+
|
361
|
+
get_object_by_value(table, value, column, *args) || begin
|
362
|
+
raise Kiss::FileNotFoundError::Object, "could not find #{table.to_s.singularize} with #{column || 'id'}=#{value} (arg #{index})"
|
363
|
+
end
|
95
364
|
end
|
96
365
|
|
97
366
|
# Returns arg_object, or new object of specified table if arg_object fails.
|
98
|
-
def arg_object_or_new(table,
|
99
|
-
|
367
|
+
def arg_object_or_new(table, *args)
|
368
|
+
begin
|
369
|
+
arg_object(table, *args)
|
370
|
+
rescue
|
371
|
+
# remove non-hash arguments, looking for desired_values
|
372
|
+
desired_values = args.last.is_a?(Hash) ? args.last : {}
|
373
|
+
dbm[table].new(desired_values)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def next_arg_index
|
378
|
+
@_arg_index += 1
|
379
|
+
end
|
380
|
+
|
381
|
+
def next_arg_object(table, *args)
|
382
|
+
arg_object(table, next_arg_index, *args)
|
100
383
|
end
|
101
384
|
|
102
|
-
|
385
|
+
def next_arg_object_or_new(table, *args)
|
386
|
+
arg_object_or_new(table, next_arg_index, *args)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Returns database model object from specified table, with column matching param
|
103
390
|
# of specified key (defaults to singular form of table name). Raises error if
|
104
391
|
# object not found in database or object data does not match desired_values.
|
105
|
-
def param_object(table, key = nil,
|
392
|
+
def param_object(table, key = nil, column = nil, *args)
|
106
393
|
key ||= table.to_s.singularize
|
107
|
-
|
394
|
+
get_object_by_value(table, params[key.to_s], column, *args)
|
108
395
|
end
|
109
396
|
|
110
397
|
# Returns param_object, or new object of specified table if param_object fails.
|
111
|
-
def param_object_or_new(table,
|
112
|
-
param_object(table,
|
398
|
+
def param_object_or_new(table, *args)
|
399
|
+
param_object(table, *args) rescue begin
|
400
|
+
desired_values = args.last.is_a?(Hash) ? args.last : {}
|
401
|
+
dbm[table].new(desired_values)
|
402
|
+
end
|
113
403
|
end
|
114
404
|
|
115
405
|
# Validates param of specified key against specified format.
|
116
|
-
def validate_param(key, format,
|
117
|
-
|
406
|
+
def validate_param(key, format, options = {})
|
407
|
+
@_params[key.to_s].validate(format, options.merge(:label => "param '#{key}'"))
|
118
408
|
end
|
119
409
|
|
120
410
|
# Validates argument of specified index against specified format.
|
121
|
-
def validate_arg(
|
122
|
-
|
411
|
+
def validate_arg(format, index = 0, options = {})
|
412
|
+
@_args[index].validate(format, options.merge(:label => "arg #{index}"))
|
123
413
|
end
|
124
414
|
|
125
|
-
# Creates and invokes new Kiss::Mailer instance to send email message via
|
126
|
-
def
|
127
|
-
|
128
|
-
:
|
415
|
+
# Creates and invokes new Kiss::Mailer instance to send email message via SMTP.
|
416
|
+
def new_email(options = {})
|
417
|
+
request.new_email({
|
418
|
+
:data => data
|
129
419
|
}.merge(options))
|
130
420
|
end
|
131
|
-
|
132
|
-
# Creates and invokes new Kiss::Mailer instance to send email message via SMTP.
|
421
|
+
|
133
422
|
def send_email(options = {})
|
134
|
-
|
135
|
-
:data => data,
|
136
|
-
:controller => controller
|
137
|
-
}.merge(options)).send
|
423
|
+
new_email(options).send
|
138
424
|
end
|
139
|
-
|
140
|
-
# Sends HTTP 302 response to redirect client agent to specified URL.
|
141
|
-
def redirect_url(*args)
|
142
|
-
controller.redirect_url(*args)
|
143
|
-
end
|
144
|
-
|
425
|
+
|
145
426
|
# Redirects to specified action path, which may also include arguments.
|
146
427
|
def redirect_action(action, options = {})
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
428
|
+
# need to rewrite this if current action class looks up objects
|
429
|
+
# by some other column than id
|
430
|
+
if options.has_key?(:object)
|
431
|
+
dest_object = options[:object]
|
432
|
+
column = self.class.parent_class.object_options[:column]
|
433
|
+
action = "#{@_object_part && '../'}#{"#{dest_object[column]}/" if dest_object}#{action}"
|
434
|
+
end
|
435
|
+
request.redirect_action( absolute_uri(action), options )
|
152
436
|
end
|
153
437
|
|
154
|
-
|
155
|
-
|
156
|
-
|
438
|
+
# Redirects to index of the same action group.
|
439
|
+
def redirect_index(options = {})
|
440
|
+
redirect_action('', options.merge(:object => nil))
|
157
441
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
442
|
+
|
443
|
+
# Redirects to the view action on the object.
|
444
|
+
def redirect_object(object = nil, options = {})
|
445
|
+
if object.is_a?(Hash)
|
446
|
+
options = object
|
447
|
+
object = nil
|
448
|
+
end
|
449
|
+
object ||= options[:object] || @_object
|
450
|
+
if object && !object.new?
|
451
|
+
redirect_action('', {:object => object}.merge(options))
|
452
|
+
else
|
453
|
+
redirect_index(options)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
alias_method :redirect_view, :redirect_object
|
457
|
+
|
458
|
+
# Redirects to index of the same action group.
|
459
|
+
def redirect_pop(options = {})
|
460
|
+
if @breadcrumbs.empty?
|
461
|
+
redirect_index(options)
|
462
|
+
else
|
463
|
+
redirect_action(@breadcrumbs.last.last, options)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
alias_method :redirect_breadcrumb, :redirect_pop
|
467
|
+
|
468
|
+
def render_action(path, options = {})
|
469
|
+
action = request.render_action(
|
470
|
+
absolute_uri(path),
|
471
|
+
options[:params] || {}, {:layout => nil}
|
472
|
+
)
|
473
|
+
action.output
|
474
|
+
end
|
475
|
+
|
476
|
+
# Convert specified action path by prefixing current action subdir,
|
161
477
|
# unless specified path starts with slash (/).
|
162
478
|
def app_absolute_path(path)
|
163
|
-
path
|
164
|
-
path
|
479
|
+
path !~ /\A\// ? self.class.subdir + '/' + path : path
|
165
480
|
end
|
166
|
-
|
481
|
+
|
482
|
+
def absolute_uri(relative_uri)
|
483
|
+
self.class.absolute_uri(relative_uri, action_path)
|
484
|
+
end
|
485
|
+
|
167
486
|
# Return string result from rendering specified string or template options.
|
168
487
|
def render_to_string(options)
|
169
488
|
if options.is_a?(String)
|
@@ -172,35 +491,57 @@ class Kiss
|
|
172
491
|
return options[:text].to_s
|
173
492
|
end
|
174
493
|
|
175
|
-
|
494
|
+
if options.is_a?(Hash)
|
495
|
+
@title = options[:title] if options.has_key?(:title)
|
496
|
+
@layout = options[:layout] if options.has_key?(:layout)
|
497
|
+
@breadcrumbs = options[:breadcrumbs] if options.has_key?(:breadcrumbs)
|
498
|
+
end
|
499
|
+
|
500
|
+
@@template_class ||= Kiss::Template.get_root_class(controller, controller.template_dir)
|
501
|
+
|
502
|
+
content = options[:content].is_a?(String) ? begin
|
503
|
+
template_class = @@template_class.get_subclass_from_path(self.class.subdir, 'rhtml')
|
504
|
+
options[:content]
|
505
|
+
end : begin
|
506
|
+
path = @@template_class.get_template_path(
|
507
|
+
{
|
508
|
+
:template => template,
|
509
|
+
:subdir => self.class.subdir,
|
510
|
+
:extension => (extension == 'xls') ? 'txt' : extension
|
511
|
+
}.merge(options)
|
512
|
+
)
|
513
|
+
template_class = @@template_class.get_subclass_from_path(path)
|
514
|
+
template_class.new(request, self).call
|
515
|
+
end
|
176
516
|
|
177
517
|
unless defined?(@layout)
|
178
|
-
@layout =
|
179
|
-
(extension == 'rhtml' ? Kiss.layout : nil)
|
518
|
+
@layout = extension == 'rhtml' ? self.class.layout || (template_class && template_class.layout) || '/_layout.rhtml' : nil
|
180
519
|
end
|
181
|
-
content = options[:content].is_a?(String) ? options[:content] : process({
|
182
|
-
:template => template,
|
183
|
-
:extension => extension
|
184
|
-
}.merge(options))
|
185
520
|
|
521
|
+
i = 0
|
186
522
|
while @layout
|
187
|
-
|
523
|
+
ext = options[:extension] || extension
|
524
|
+
layout_path = @@template_class.get_template_path(
|
188
525
|
:template => @layout,
|
189
|
-
:
|
526
|
+
:subdir => self.class.subdir,
|
527
|
+
:extension => (ext == 'xls') ? 'txt' : ext
|
190
528
|
) rescue break
|
191
|
-
|
192
|
-
#
|
193
|
-
|
194
|
-
@layout = nil
|
529
|
+
|
530
|
+
# cache layout for comparison after render
|
531
|
+
prev_layout = @layout
|
195
532
|
|
196
533
|
# process layout file
|
197
|
-
|
534
|
+
layout_class = @@template_class.get_subclass_from_path(layout_path)
|
535
|
+
content = layout_class.new(request, self).call(content)
|
536
|
+
|
537
|
+
if @layout == prev_layout
|
538
|
+
@layout = layout_class.parent_class.parent_class.layout
|
539
|
+
end
|
198
540
|
end
|
199
|
-
# add a base tag in case of arguments appended to request URI
|
200
541
|
|
201
|
-
|
202
|
-
if (base_url)
|
203
|
-
content =
|
542
|
+
# add a base tag in case of arguments appended to request URI
|
543
|
+
if extension == 'rhtml' && (base_url = options.has_key?(:base_url) ? options[:base_url] : @base_url)
|
544
|
+
content = content.prepend_html(%Q(<base href="#{base_url}" />), 'head')
|
204
545
|
end
|
205
546
|
|
206
547
|
content
|
@@ -208,60 +549,89 @@ class Kiss
|
|
208
549
|
|
209
550
|
# Render and return response to Rack.
|
210
551
|
def render(options = {})
|
211
|
-
|
212
|
-
|
552
|
+
Dir.chdir(controller.project_dir)
|
553
|
+
|
554
|
+
@_output = render_to_string(options)
|
555
|
+
@_output_options = options
|
213
556
|
throw :kiss_action_done
|
214
557
|
end
|
215
|
-
|
216
|
-
# Placeholder for generic actions that do nothing but render template.
|
217
|
-
# render is called from Kiss#call after this method returns.
|
218
|
-
def call; end
|
219
558
|
|
220
559
|
# Does nothing by default; override this method in your _action.rb or
|
221
560
|
# other action files to specify authentication behavior.
|
222
561
|
def authenticate; end
|
223
562
|
|
224
|
-
#
|
225
|
-
#
|
226
|
-
def
|
563
|
+
# Callback method: called after action handler initialized. Does nothing
|
564
|
+
# by default; override this method in your _action.rb or other action files.
|
565
|
+
def after_initialize; end
|
566
|
+
|
567
|
+
# Callback method: called before action call. Does nothing by default;
|
568
|
+
# override this method in your _action.rb or other action files.
|
569
|
+
def before_call; end
|
570
|
+
|
571
|
+
# Placeholder for generic actions that do nothing but render template.
|
572
|
+
# render is called from Kiss#call after this method returns.
|
573
|
+
def call; end
|
574
|
+
|
575
|
+
# Callback method: called after action call. Does nothing by default;
|
576
|
+
# override this method in your _action.rb or other action files.
|
577
|
+
def after_call; end
|
227
578
|
|
228
579
|
# Ensure that action requested via SSL; redirects to same action with
|
229
580
|
# https protocol if protocol is not https.
|
230
581
|
def force_ssl(options = {})
|
231
582
|
unless protocol == 'https'
|
232
|
-
redirect_action(
|
583
|
+
redirect_action(request.path_with_query_string, options.merge( :protocol => 'https' ))
|
233
584
|
end
|
234
585
|
end
|
235
586
|
alias_method :force_https, :force_ssl
|
587
|
+
|
588
|
+
def is_index?
|
589
|
+
self.class.name == self.class.subdir + '/index'
|
590
|
+
end
|
591
|
+
|
592
|
+
def is_view?
|
593
|
+
self.class.name == self.class.subdir + '/view'
|
594
|
+
end
|
595
|
+
|
596
|
+
def is_edit?
|
597
|
+
self.class.name == self.class.subdir + '/edit'
|
598
|
+
end
|
599
|
+
|
600
|
+
def is_delete?
|
601
|
+
self.class.name == self.class.subdir + '/delete'
|
602
|
+
end
|
236
603
|
|
237
604
|
# Creates and adds form to current action, using specified attributes.
|
238
|
-
def add_form(attrs = {})
|
239
|
-
# make sure form not already defined for this request
|
240
|
-
@forms ||= {}
|
241
|
-
attrs[:name] ||= @action
|
242
|
-
form_name = attrs[:name]
|
243
|
-
raise "page contains multiple forms named '#{form_name}'" if @forms.has_key?(form_name)
|
244
|
-
|
605
|
+
def add_form(attrs = {}, &block)
|
245
606
|
# create form
|
246
|
-
|
247
|
-
|
248
|
-
:action =>
|
249
|
-
:
|
250
|
-
}.merge(attrs))
|
607
|
+
temp_form = Kiss::Form.new({
|
608
|
+
:name => attrs[:action] || action_path,
|
609
|
+
:action => app + request.path_with_query_string,
|
610
|
+
:delegate => self
|
611
|
+
}.merge(attrs), &block)
|
612
|
+
|
613
|
+
# make sure form not already defined for this request
|
614
|
+
@_forms ||= {}
|
615
|
+
raise "page contains multiple forms named '#{temp_form.name}'" if @_forms.has_key?(temp_form.name)
|
616
|
+
@_forms[temp_form.name] = @_form = temp_form
|
251
617
|
|
252
|
-
# add data from
|
253
|
-
if params['form'] == @
|
254
|
-
@
|
255
|
-
@
|
618
|
+
# add data from request to form
|
619
|
+
if params['form'] == @_form.name
|
620
|
+
@_form.submitted = true
|
621
|
+
@_form.params = params
|
256
622
|
end
|
257
623
|
|
258
|
-
@
|
624
|
+
@_form
|
259
625
|
end
|
260
|
-
|
626
|
+
|
261
627
|
# Returns most recently added form.
|
262
628
|
# Shorthand for forms[0] when there is only one form.
|
263
629
|
def form
|
264
|
-
@
|
630
|
+
@_form
|
631
|
+
end
|
632
|
+
|
633
|
+
def invoke_action(path, params = {})
|
634
|
+
request.invoke_action(path, params).output
|
265
635
|
end
|
266
636
|
end
|
267
637
|
end
|