reactive 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/History.txt +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest.txt +60 -0
  4. data/README.txt +130 -0
  5. data/Rakefile +14 -0
  6. data/app_generators/reactive/USAGE +11 -0
  7. data/app_generators/reactive/reactive_generator.rb +160 -0
  8. data/app_generators/reactive/templates/README +130 -0
  9. data/app_generators/reactive/templates/Rakefile +10 -0
  10. data/app_generators/reactive/templates/app/controllers/application_controller.rb +2 -0
  11. data/app_generators/reactive/templates/app/helpers/application_helper.rb +2 -0
  12. data/app_generators/reactive/templates/config/boot.rb +94 -0
  13. data/app_generators/reactive/templates/config/databases/frontbase.yml +28 -0
  14. data/app_generators/reactive/templates/config/databases/mysql.yml +54 -0
  15. data/app_generators/reactive/templates/config/databases/oracle.yml +39 -0
  16. data/app_generators/reactive/templates/config/databases/postgresql.yml +48 -0
  17. data/app_generators/reactive/templates/config/databases/sqlite2.yml +16 -0
  18. data/app_generators/reactive/templates/config/databases/sqlite3.yml +19 -0
  19. data/app_generators/reactive/templates/config/empty.log +0 -0
  20. data/app_generators/reactive/templates/config/environment.rb +11 -0
  21. data/app_generators/reactive/templates/script/destroy +12 -0
  22. data/app_generators/reactive/templates/script/generate +12 -0
  23. data/app_generators/reactive/templates/script/run +5 -0
  24. data/app_generators/reactive/templates/script/win_script.cmd +1 -0
  25. data/bin/reactive +16 -0
  26. data/lib/code_statistics.rb +107 -0
  27. data/lib/controller.rb +23 -0
  28. data/lib/controller/base.rb +442 -0
  29. data/lib/controller/filters.rb +767 -0
  30. data/lib/controller/flash.rb +161 -0
  31. data/lib/controller/helpers.rb +204 -0
  32. data/lib/controller/layout.rb +326 -0
  33. data/lib/dispatcher.rb +46 -0
  34. data/lib/generated_attribute.rb +40 -0
  35. data/lib/initializer.rb +425 -0
  36. data/lib/named_base_generator.rb +92 -0
  37. data/lib/reactive.rb +6 -0
  38. data/lib/request.rb +17 -0
  39. data/lib/source_annotation_extractor.rb +62 -0
  40. data/lib/tasks/annotations.rake +23 -0
  41. data/lib/tasks/databases.rake +347 -0
  42. data/lib/tasks/log.rake +9 -0
  43. data/lib/tasks/misc.rake +5 -0
  44. data/lib/tasks/reactive.rb +16 -0
  45. data/lib/tasks/statistics.rake +17 -0
  46. data/lib/tasks/testing.rake +118 -0
  47. data/lib/version.rb +9 -0
  48. data/lib/view.rb +1 -0
  49. data/lib/view/base.rb +33 -0
  50. data/reactive_generators/model/USAGE +27 -0
  51. data/reactive_generators/model/model_generator.rb +52 -0
  52. data/reactive_generators/model/templates/fixtures.yml +19 -0
  53. data/reactive_generators/model/templates/migration.rb +16 -0
  54. data/reactive_generators/model/templates/model.rb +2 -0
  55. data/reactive_generators/model/templates/unit_test.rb +8 -0
  56. data/reactive_generators/scaffold/USAGE +26 -0
  57. data/reactive_generators/scaffold/scaffold_generator.rb +75 -0
  58. data/reactive_generators/scaffold/templates/controller.rb +51 -0
  59. data/reactive_generators/scaffold/templates/functional_test.rb +49 -0
  60. data/reactive_generators/scaffold/templates/helper.rb +2 -0
  61. metadata +142 -0
@@ -0,0 +1,23 @@
1
+
2
+ require 'controller/base'
3
+ require 'controller/flash'
4
+ require 'controller/filters'
5
+ require 'controller/layout'
6
+ require 'controller/helpers'
7
+
8
+ Reactive::Controller::Base.class_eval do
9
+ include Reactive::Controller::Flash
10
+ include Reactive::Controller::Filters
11
+ include Reactive::Controller::Layout
12
+ include Reactive::Controller::Helpers
13
+
14
+ =begin
15
+ include ActionController::Benchmarking
16
+ include ActionController::Rescue
17
+ include ActionController::MimeResponds
18
+ include ActionController::Verification
19
+ include ActionController::Streaming
20
+ include ActionController::Components
21
+ include ActionController::RecordIdentifier
22
+ =end
23
+ end
@@ -0,0 +1,442 @@
1
+
2
+ module Reactive
3
+ module Controller
4
+
5
+ class Error < StandardError; end #:nodoc:
6
+ class UnknownAction < Error; end #:nodoc:
7
+ class MissingTemplate < Error; end #:nodoc:
8
+ class RenderError < Error; end #:nodoc:
9
+ class DoubleRenderError < Error #:nodoc:
10
+ DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
11
+
12
+ def initialize(message = nil)
13
+ super(message || DEFAULT_MESSAGE)
14
+ end
15
+ end
16
+
17
+ class Base
18
+
19
+ # The logger is used for generating information on the action run-time (including benchmarking) if available.
20
+ # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
21
+ cattr_accessor :logger
22
+
23
+ # Determines which template class should be used by the Controllers.
24
+ cattr_accessor :template_class
25
+
26
+ # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
27
+ cattr_accessor :ignore_missing_templates
28
+
29
+ # Returns the name of the action this controller is processing.
30
+ attr_reader :action_name
31
+
32
+ # Holds the request object that will be used by the dispatcher.
33
+ attr_internal :request
34
+
35
+ # Holds the response object that will be used by the dispatcher.
36
+ attr_internal :response
37
+
38
+ # Holds a hash of all the parameters passed to the action. Accessed like <tt>params["post_id"]</tt>
39
+ # to get the post_id. No type casts are made, so all values are of the initial type of the request.
40
+ attr_internal :params
41
+
42
+ # Templates that are exempt from layouts
43
+ @@exempt_from_layout = Set.new #Set.new([/\.rb$/])
44
+
45
+ class << self
46
+
47
+ def process(request, response, method = :perform_action, *arguments)
48
+ new.process(request, response, method, *arguments)
49
+ end
50
+
51
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
52
+ def controller_class_name
53
+ @controller_class_name ||= name.demodulize
54
+ end
55
+
56
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
57
+ def controller_name
58
+ @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
59
+ end
60
+
61
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
62
+ def controller_path
63
+ @controller_path ||= name.gsub(/Controller$/, '').underscore
64
+ end
65
+
66
+ # Return an array containing the names of public methods that have been marked hidden from the action processor.
67
+ # By default, all methods defined in ActionController::Base and included modules are hidden.
68
+ # More methods can be hidden using <tt>hide_actions</tt>.
69
+ def hidden_actions
70
+ unless read_inheritable_attribute(:hidden_actions)
71
+ write_inheritable_attribute(:hidden_actions, Controller::Base.public_instance_methods.map(&:to_s))
72
+ end
73
+
74
+ read_inheritable_attribute(:hidden_actions)
75
+ end
76
+
77
+ # Hide each of the given methods from being callable as actions.
78
+ def hide_action(*names)
79
+ write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
80
+ end
81
+
82
+ ## View load paths determine the bases from which template references can be made. So a call to
83
+ ## render("test/template") will be looked up in the view load paths array and the closest match will be
84
+ ## returned.
85
+ def view_paths
86
+ @view_paths || superclass.view_paths
87
+ end
88
+
89
+ def view_paths=(value)
90
+ @view_paths = value
91
+ end
92
+
93
+ # Adds a view_path to the front of the view_paths array.
94
+ # If the current class has no view paths, copy them from
95
+ # the superclass. This change will be visible for all future requests.
96
+ #
97
+ # ArticleController.prepend_view_path("views/default")
98
+ # ArticleController.prepend_view_path(["views/default", "views/custom"])
99
+ #
100
+ def prepend_view_path(path)
101
+ @view_paths = superclass.view_paths.dup if @view_paths.nil?
102
+ view_paths.unshift(*path)
103
+ end
104
+
105
+ # Adds a view_path to the end of the view_paths array.
106
+ # If the current class has no view paths, copy them from
107
+ # the superclass. This change will be visible for all future requests.
108
+ #
109
+ # ArticleController.append_view_path("views/default")
110
+ # ArticleController.append_view_path(["views/default", "views/custom"])
111
+ #
112
+ def append_view_path(path)
113
+ @view_paths = superclass.view_paths.dup if @view_paths.nil?
114
+ view_paths.push(*path)
115
+ end
116
+
117
+ end
118
+
119
+ # Extracts the action_name from the request parameters and performs that action.
120
+ def process(request, response, method = :perform_action, *arguments) #:nodoc:
121
+ initialize_template_class(response)
122
+ assign_params(request.params)
123
+ assign_shortcuts(request, response)
124
+ forget_variables_added_to_assigns
125
+
126
+ log_processing
127
+ send(method, *arguments)
128
+
129
+ response
130
+ ensure
131
+ process_cleanup
132
+ end
133
+
134
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
135
+ def controller_class_name
136
+ self.class.controller_class_name
137
+ end
138
+
139
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
140
+ def controller_name
141
+ self.class.controller_name
142
+ end
143
+
144
+ # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
145
+ def controller_path
146
+ self.class.controller_path
147
+ end
148
+
149
+ self.view_paths = []
150
+
151
+ # View load paths for controller.
152
+ def view_paths
153
+ (@template || self.class).view_paths
154
+ end
155
+
156
+ def view_paths=(value)
157
+ (@template || self.class).view_paths = value
158
+ end
159
+
160
+ # Adds a view_path to the front of the view_paths array.
161
+ # This change affects the current request only.
162
+ #
163
+ # self.prepend_view_path("views/default")
164
+ # self.prepend_view_path(["views/default", "views/custom"])
165
+ #
166
+ def prepend_view_path(path)
167
+ (@template || self.class).prepend_view_path(path)
168
+ end
169
+
170
+ # Adds a view_path to the end of the view_paths array.
171
+ # This change affects the current request only.
172
+ #
173
+ # self.append_view_path("views/default")
174
+ # self.append_view_path(["views/default", "views/custom"])
175
+ #
176
+ def append_view_path(path)
177
+ (@template || self.class).append_view_path(path)
178
+ end
179
+
180
+ protected
181
+ # render
182
+ def render(options = nil, &block) #:doc:
183
+ raise DoubleRenderError, "Can only render or redirect once per action" if performed?
184
+
185
+ if options.nil?
186
+ return render_for_file(default_template_name, nil, true)
187
+ else
188
+ if options == :update
189
+ options = { :update => true }
190
+ elsif !options.is_a?(Hash)
191
+ raise RenderError, "You called render with invalid options : #{options}"
192
+ end
193
+ end
194
+
195
+ if text = options[:text]
196
+ render_for_text(text, options[:status])
197
+
198
+ else
199
+ if file = options[:file]
200
+ render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
201
+
202
+ elsif template = options[:template]
203
+ render_for_file(template, options[:status], true)
204
+
205
+ elsif inline = options[:inline]
206
+ add_variables_to_assigns
207
+ render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
208
+
209
+ elsif action_name = options[:action]
210
+ template = default_template_name(action_name.to_s)
211
+ if options[:layout] && !template_exempt_from_layout?(template)
212
+ render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
213
+ else
214
+ render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
215
+ end
216
+
217
+ elsif partial = options[:partial]
218
+ partial = default_template_name if partial == true
219
+ add_variables_to_assigns
220
+
221
+ if collection = options[:collection]
222
+ render_for_text(
223
+ @template.send!(:render_partial_collection, partial, collection,
224
+ options[:spacer_template], options[:locals]), options[:status]
225
+ )
226
+ else
227
+ render_for_text(
228
+ @template.send!(:render_partial, partial,
229
+ ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
230
+ )
231
+ end
232
+
233
+ elsif options[:update]
234
+ raise NotImplementedError
235
+ add_variables_to_assigns
236
+ @template.send! :evaluate_assigns
237
+
238
+ generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
239
+ response.content_type = Mime::JS
240
+ render_for_text(generator.to_s)
241
+
242
+ elsif options[:nothing]
243
+ render_for_text(" ", options[:status])
244
+
245
+ else
246
+ render_for_file(default_template_name, options[:status], true)
247
+ end
248
+ end
249
+ end
250
+
251
+ def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
252
+ #puts "Controller#render_for_file(#{template_path}) request: #{request.inspect}"
253
+ add_variables_to_assigns
254
+ assert_existence_of_template_file(template_path) if use_full_path
255
+ logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
256
+ render_for_text(@template.render_file(template_path, use_full_path, locals), status)
257
+ end
258
+
259
+ def render_for_text(text = nil, status = nil) #:nodoc:
260
+ @performed_render = true
261
+ response.result = text.to_s
262
+ # when will we get a proc? TODO investigate: response.body = text.is_a?(Proc) ? text : text.to_s
263
+ end
264
+
265
+ # Redirects to the target specified in +options+. This parameter can take one of three forms:
266
+ #
267
+ # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
268
+ # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
269
+ #
270
+ # Examples:
271
+ # redirect_to :action => "show", :id => 5
272
+ # redirect_to post
273
+ # redirect_to articles_url
274
+ #
275
+ def redirect_to(options = {}) #:doc:
276
+ raise DoubleRenderError if performed?
277
+ logger.info("Redirected to #{options}") if logger && logger.info?
278
+ response.redirected_to = options
279
+ @performed_redirect = true
280
+ end
281
+
282
+ def assign_params(params)
283
+ @action_name = params[:action] || 'index'
284
+ @_params = params.clone
285
+ @_params.delete(:controller)
286
+ @_params.delete(:action)
287
+ end
288
+
289
+ def initialize_template_class(response)
290
+ unless @@template_class
291
+ raise "You must assign a template class through Reactive::Controller::Base.template_class= before processing a request"
292
+ end
293
+
294
+ response.template = @@template_class.new(view_paths, {}, self)
295
+ response.template.extend self.class.master_helper_module
296
+ response.redirected_to = nil
297
+ @performed_render = @performed_redirect = false
298
+ end
299
+
300
+ def assign_shortcuts(request, response)
301
+ @_request = request
302
+ @_response = response
303
+ @template = response.template
304
+ @assigns = @_response.template.assigns
305
+ end
306
+
307
+ def log_processing
308
+ if logger && logger.info?
309
+ logger.info "\n\nProcessing #{controller_class_name}\##{action_name}"
310
+ logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(@_params).inspect : @_params.inspect}"
311
+ end
312
+ end
313
+
314
+ def default_render #:nodoc:
315
+ render
316
+ end
317
+
318
+ def perform_action
319
+ if self.class.action_methods.include?(action_name)
320
+ send(action_name)
321
+ default_render unless performed?
322
+ elsif respond_to? :method_missing
323
+ method_missing action_name
324
+ default_render unless performed?
325
+ elsif template_exists? && template_public?
326
+ default_render
327
+ else
328
+ raise UnknownAction, "No action responded to #{action_name}", caller
329
+ end
330
+ end
331
+
332
+ def perform_init
333
+ template_name = "application"
334
+ #TODO: refactor this
335
+ if true #if template_exists?(template_name) && template_public?(template_name)
336
+ render :template => template_name, :layout => false
337
+ else
338
+ raise MissingTemplate, "Missing #{template_name} (the initial view) in view path #{view_paths.join(':')}"
339
+ end
340
+ end
341
+
342
+ def performed?
343
+ @performed_render || @performed_redirect
344
+ end
345
+
346
+ def action_methods
347
+ self.class.action_methods
348
+ end
349
+
350
+ def self.action_methods
351
+ @action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
352
+ end
353
+
354
+ def add_variables_to_assigns
355
+ unless @variables_added
356
+ add_instance_variables_to_assigns
357
+ #TODO add_class_variables_to_assigns if view_controller_internals
358
+ @variables_added = true
359
+ end
360
+ end
361
+
362
+ def forget_variables_added_to_assigns
363
+ @variables_added = nil
364
+ end
365
+
366
+ def reset_variables_added_to_assigns
367
+ @template.instance_variable_set("@assigns_added", nil)
368
+ end
369
+
370
+ def add_instance_variables_to_assigns
371
+ @@protected_variables_cache ||= Set.new(protected_instance_variables)
372
+ instance_variables.each do |var|
373
+ next if @@protected_variables_cache.include?(var)
374
+ @assigns[var[1..-1]] = instance_variable_get(var)
375
+ end
376
+ end
377
+
378
+ def add_class_variables_to_assigns
379
+ %w(view_paths logger template_class ignore_missing_templates).each do |cvar|
380
+ @assigns[cvar] = self.send(cvar)
381
+ end
382
+ end
383
+
384
+ def protected_instance_variables
385
+ # if view_controller_internals
386
+ %w(@assigns @performed_redirect @performed_render)
387
+ # else
388
+ # %w(@assigns @performed_redirect @performed_render
389
+ # @_request @request @_response @response @_params @params
390
+ # @template @request_origin @parent_controller)
391
+ # end
392
+ end
393
+
394
+ def template_exists?(template_name = default_template_name)
395
+ @template.file_exists?(template_name)
396
+ end
397
+
398
+ def template_public?(template_name = default_template_name)
399
+ @template.file_public?(template_name)
400
+ end
401
+
402
+ def template_exempt_from_layout?(template_name = default_template_name)
403
+ extension = @template && @template.pick_template_extension(template_name)
404
+ name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
405
+ @@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
406
+ end
407
+
408
+ def assert_existence_of_template_file(template_name)
409
+ return true
410
+ unless template_exists?(template_name) || ignore_missing_templates
411
+ full_template_path = template_name.include?('.') ? template_name : "#{template_name}.#{@template.template_format}.rb"
412
+ display_paths = view_paths.join(':')
413
+ template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
414
+ raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
415
+ end
416
+ end
417
+
418
+ def default_template_name(action_name = self.action_name)
419
+ if action_name
420
+ action_name = action_name.to_s
421
+ if action_name.include?('/') && template_path_includes_controller?(action_name)
422
+ action_name = strip_out_controller(action_name)
423
+ end
424
+ end
425
+ "#{self.class.controller_path}/#{action_name}"
426
+ end
427
+
428
+ def strip_out_controller(path)
429
+ path.split('/', 2).last
430
+ end
431
+
432
+ def template_path_includes_controller?(path)
433
+ self.class.controller_path.split('/')[-1] == path.split('/')[0]
434
+ end
435
+
436
+ def process_cleanup
437
+ end
438
+ end
439
+
440
+ end
441
+
442
+ end