racket-mvc 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -5
  3. data/lib/racket.rb +25 -7
  4. data/lib/racket/application.rb +70 -136
  5. data/lib/racket/controller.rb +95 -38
  6. data/lib/racket/current.rb +1 -1
  7. data/lib/racket/helpers/file.rb +7 -5
  8. data/lib/racket/helpers/routing.rb +5 -5
  9. data/lib/racket/helpers/sass.rb +11 -11
  10. data/lib/racket/helpers/view.rb +8 -5
  11. data/lib/racket/plugins/base.rb +1 -1
  12. data/lib/racket/plugins/sass.rb +1 -1
  13. data/lib/racket/request.rb +3 -3
  14. data/lib/racket/response.rb +1 -1
  15. data/lib/racket/router.rb +31 -12
  16. data/lib/racket/session.rb +3 -3
  17. data/lib/racket/settings/application.rb +27 -33
  18. data/lib/racket/settings/base.rb +19 -8
  19. data/lib/racket/settings/controller.rb +9 -7
  20. data/lib/racket/settings/defaults.rb +81 -0
  21. data/lib/racket/utils.rb +19 -15
  22. data/lib/racket/utils/application.rb +4 -114
  23. data/lib/racket/utils/application/handler_stack.rb +163 -0
  24. data/lib/racket/utils/application/logger.rb +72 -0
  25. data/lib/racket/utils/application/registry_builder.rb +88 -0
  26. data/lib/racket/utils/application/stateless_services.rb +73 -0
  27. data/lib/racket/utils/exceptions.rb +3 -3
  28. data/lib/racket/utils/file_system.rb +75 -47
  29. data/lib/racket/utils/helpers.rb +35 -13
  30. data/lib/racket/utils/routing.rb +62 -46
  31. data/lib/racket/utils/views.rb +19 -187
  32. data/lib/racket/utils/views/renderer.rb +75 -0
  33. data/lib/racket/utils/views/template_cache.rb +126 -0
  34. data/lib/racket/utils/views/template_locator.rb +83 -0
  35. data/lib/racket/utils/views/template_resolver.rb +112 -0
  36. data/lib/racket/version.rb +2 -2
  37. data/lib/racket/view_manager.rb +12 -4
  38. data/rake/utils.rb +5 -5
  39. data/spec/_custom.rb +69 -19
  40. data/spec/_default.rb +60 -44
  41. data/spec/_plugin.rb +12 -14
  42. data/spec/_template_cache.rb +176 -0
  43. data/spec/racket.rb +10 -13
  44. data/spec/test_custom_app/controllers/sub1/custom_sub_controller_1.rb +1 -1
  45. data/spec/test_custom_app/controllers/sub3/custom_sub_controller_3.rb +19 -1
  46. data/spec/test_custom_app/controllers/sub5/custom_sub_controller_5.rb +8 -0
  47. data/spec/test_custom_app/files/stuff.rb +3 -0
  48. data/spec/test_custom_app/files/triplet.erb +5 -0
  49. data/spec/test_custom_app/templates/sub5/text.erb +3 -0
  50. data/spec/test_default_app/controllers/default_root_controller.rb +3 -0
  51. data/spec/test_default_app/controllers/sub1/default_sub_controller_1.rb +1 -1
  52. metadata +52 -11
@@ -1,5 +1,5 @@
1
1
  # Racket - The noisy Rack MVC framework
2
- # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
3
  #
4
4
  # This file is part of Racket.
5
5
  #
@@ -22,9 +22,25 @@ module Racket
22
22
  module Helpers
23
23
  # Cache for helpers, ensuring that helpers get loaded exactly once.
24
24
  class HelperCache
25
- def initialize(helper_dir)
25
+ # Returns a service proc that can be used by the registry.
26
+ #
27
+ # @param [Hash] _options (unused)
28
+ # @return [Proc]
29
+ def self.service(_options = {})
30
+ lambda do |reg|
31
+ new(
32
+ reg.application_settings.helper_dir,
33
+ reg.application_logger,
34
+ reg.utils
35
+ )
36
+ end
37
+ end
38
+
39
+ def initialize(helper_dir, logger, utils)
26
40
  @helper_dir = helper_dir
27
41
  @helpers = {}
42
+ @logger = logger
43
+ @utils = utils
28
44
  end
29
45
 
30
46
  # Loads helper files and return the loadad modules as a hash. Any helper files that
@@ -51,20 +67,24 @@ module Racket
51
67
 
52
68
  def load_helper_file(helper)
53
69
  require_helper_file(helper)
54
- self.class.load_helper_module(helper)
70
+ load_helper_module(helper)
55
71
  end
56
72
 
57
73
  def require_helper_file(helper)
58
- loaded = Utils.safe_require("racket/helpers/#{helper}")
59
- Utils.safe_require(Utils.build_path(@helper_dir, helper).to_s) if !loaded && @helper_dir
74
+ loaded = @utils.safe_require("racket/helpers/#{helper}")
75
+ @utils.safe_require(@utils.build_path(@helper_dir, helper).to_s) if !loaded && @helper_dir
60
76
  end
61
77
 
62
- def self.load_helper_module(helper)
78
+ # Loads a helper module
79
+ #
80
+ # @param [Symbol] helper
81
+ # @return [Module]
82
+ def load_helper_module(helper)
63
83
  helper_module = nil
64
- Utils.run_block(NameError) do
84
+ @utils.run_block(NameError) do
65
85
  helper_module =
66
86
  Racket::Helpers.const_get(helper.to_s.split('_').collect(&:capitalize).join.to_sym)
67
- ::Racket::Application.inform_dev("Loaded helper module #{helper.inspect}.")
87
+ @logger.inform_dev("Loaded helper module #{helper.inspect}.")
68
88
  end
69
89
  helper_module
70
90
  end
@@ -73,23 +93,25 @@ module Racket
73
93
  # Applies helpers to a controller class by including the modules in the class.
74
94
  #
75
95
  # @param [Class] klass
76
- def self.apply_helpers(klass)
96
+ def apply_helpers(klass)
77
97
  klass.helper unless klass.settings.fetch(:helpers) # Makes sure default helpers are loaded.
78
98
  __apply_helpers(klass)
79
99
  nil
80
100
  end
81
101
 
82
- def self.__apply_helpers(klass)
102
+ # Applies helpers to a controller class by including the modules in the class.
103
+ #
104
+ # @param [Class] klass
105
+ # @return [Class]
106
+ def __apply_helpers(klass)
83
107
  klass.settings.fetch(:helpers).reverse_each do |pair|
84
108
  helper_key, helper = pair
85
- ::Racket::Application.inform_dev(
109
+ klass.context.logger.inform_dev(
86
110
  "Adding helper module #{helper_key.inspect} to #{klass}"
87
111
  )
88
112
  klass.send(:include, helper)
89
113
  end
90
114
  end
91
-
92
- private_class_method :__apply_helpers
93
115
  end
94
116
  end
95
117
  end
@@ -1,5 +1,5 @@
1
1
  # Racket - The noisy Rack MVC framework
2
- # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
3
  #
4
4
  # This file is part of Racket.
5
5
  #
@@ -25,10 +25,19 @@ module Racket
25
25
 
26
26
  # Class for caching actions
27
27
  class ActionCache
28
+ # Returns a service proc that can be used by the registry.
29
+ #
30
+ # @param [Hash] _options (unused)
31
+ # @return [Proc]
32
+ def self.service(_options = {})
33
+ ->(reg) { new(reg.application_logger) }
34
+ end
35
+
28
36
  attr_reader :items
29
37
 
30
- def initialize
38
+ def initialize(logger)
31
39
  @items = {}
40
+ @logger = logger
32
41
  end
33
42
 
34
43
  # Returns whether +controller_class+ is in the cache and that it contains the action
@@ -50,7 +59,7 @@ module Racket
50
59
  __add(controller_class)
51
60
  actions = @items[controller_class].to_a
52
61
  @items[controller_class] = actions
53
- ::Racket::Application.inform_dev(
62
+ @logger.inform_dev(
54
63
  "Registering actions #{actions} for #{controller_class}."
55
64
  ) && nil
56
65
  end
@@ -69,55 +78,62 @@ module Racket
69
78
  end
70
79
  end
71
80
 
72
- # Extracts the target class, target params and target action from a list of valid routes.
73
- #
74
- # @param [HttpRouter::Response] response
75
- # @return [Array]
76
- def self.extract_target(response)
77
- target_klass = response.route.dest
78
- params = response.param_values.first.reject(&:empty?)
79
- action = params.empty? ? target_klass.settings.fetch(:default_action) : params.shift.to_sym
80
- [target_klass, params, action]
81
- end
81
+ # Class responsible for dispatching requests to controllers.
82
+ class Dispatcher
83
+ # Extracts the target class, target params and target action from a list of valid routes.
84
+ #
85
+ # @param [HttpRouter::Response] response
86
+ # @return [Array]
87
+ def self.extract_target(response)
88
+ target_klass = response.route.dest
89
+ params = response.param_values.first.reject(&:empty?)
90
+ action =
91
+ if params.empty?
92
+ target_klass.settings.fetch(:default_action)
93
+ else
94
+ params.shift.to_sym
95
+ end
96
+ [target_klass, params, action]
97
+ end
82
98
 
83
- def self.call_controller(target_klass, mod)
84
- target = target_klass.new
85
- target.extend(mod)
86
- target.__run
87
- end
99
+ # Constructor
100
+ #
101
+ # @param [Hash] env
102
+ # @param [Array] target_info
103
+ def initialize(env, target_info)
104
+ @env = env
105
+ @controller_class, @params, @action = target_info
106
+ end
88
107
 
89
- # Renders a controller. This is the default action whenever a matching route for a request
90
- # is found.
91
- #
92
- # @param [Hash] env
93
- # @param [Array] target_info
94
- # @return [Array] A racket response triplet
95
- def self.render_controller(env, target_info)
96
- controller_class, params, action = target_info
108
+ # Dispatches request to a controller. This is the default action whenever a matching
109
+ # route for a request is found.
110
+ #
111
+ # @return [Array] A racket response triplet
112
+ def dispatch
113
+ # Rewrite PATH_INFO to reflect that we split out the parameters
114
+ update_path_info(@params.length)
97
115
 
98
- # Rewrite PATH_INFO to reflect that we split out the parameters
99
- update_path_info(env, params.length)
116
+ # Call controller
117
+ call_controller(Current.init(RouterParams.new(@action, @params, @env)))
118
+ end
100
119
 
101
- # Initialize and render target
102
- call_controller(
103
- controller_class,
104
- Current.init(RouterParams.new(action, params, env))
105
- )
106
- end
120
+ private
107
121
 
108
- # Updates the PATH_INFO environment variable.
109
- #
110
- # @param [Hash] env
111
- # @param [Fixnum] num_params
112
- # @return [nil]
113
- def self.update_path_info(env, num_params)
114
- env['PATH_INFO'] = env['PATH_INFO']
115
- .split('/')[0...-num_params]
116
- .join('/') unless num_params.zero?
117
- nil
118
- end
122
+ def call_controller(mod)
123
+ @controller_class.new.extend(mod).__run
124
+ end
119
125
 
120
- private_class_method :call_controller, :update_path_info
126
+ # Updates the PATH_INFO environment variable.
127
+ #
128
+ # @param [Fixnum] num_params
129
+ # @return [nil]
130
+ def update_path_info(num_params)
131
+ @env['PATH_INFO'] = @env['PATH_INFO']
132
+ .split('/')[0...-num_params]
133
+ .join('/') unless num_params.zero?
134
+ nil
135
+ end
136
+ end
121
137
  end
122
138
  end
123
139
  end
@@ -1,5 +1,5 @@
1
1
  # Racket - The noisy Rack MVC framework
2
- # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
2
+ # Copyright (C) 2015-2016 Lars Olsson <lasso@lassoweb.se>
3
3
  #
4
4
  # This file is part of Racket.
5
5
  #
@@ -18,197 +18,29 @@
18
18
 
19
19
  require 'tilt'
20
20
 
21
+ require_relative 'views/renderer.rb'
22
+ require_relative 'views/template_cache.rb'
23
+ require_relative 'views/template_locator.rb'
24
+ require_relative 'views/template_resolver.rb'
25
+
21
26
  module Racket
22
27
  module Utils
23
- # Utility functions for views.
28
+ # Namespace for view utilities
24
29
  module Views
25
- # Cache for storing templates
26
- class TemplateCache
27
- def initialize
28
- @cache = {}
29
- end
30
-
31
- # Returns a cached template. If the template has not been cached yet, this method will run a
32
- # lookup against the provided parameters.
33
- #
34
- # @param [String] path
35
- # @param [TemplateParams] template_params
36
- # @return [String|Proc|nil]
37
- def ensure_in_cache(path, template_params)
38
- return @cache[path] if @cache.key?(path)
39
- @cache[path] = TemplateLocator.calculate_path(path, template_params)
40
- end
41
- end
42
-
43
- # Class used for locating templates.
44
- class TemplateLocator
45
- def initialize(layout_base_dir, view_base_dir)
46
- @layout_base_dir = layout_base_dir
47
- @view_base_dir = view_base_dir
48
- @layout_cache = TemplateCache.new
49
- @view_cache = TemplateCache.new
50
- end
51
-
52
- # Returns the layout associated with the current request. On the first request to any action
53
- # the result is cached, meaning that the layout only needs to be looked up once.
54
- #
55
- # @param [Racket::Controller] controller
56
- # @return [String|nil]
57
- def get_layout(controller)
58
- get_template(TemplateParams.new(:layout, controller, @layout_base_dir, @layout_cache))
59
- end
60
-
61
- # Returns the view associated with the current request. On the first request to any action
62
- # the result is cached, meaning that the view only needs to be looked up once.
63
- #
64
- # @param [Racket::Controller] controller
65
- # @return [String|nil]
66
- def get_view(controller)
67
- get_template(TemplateParams.new(:view, controller, @view_base_dir, @view_cache))
68
- end
69
-
70
- private
71
-
72
- # Tries to locate a template matching +path+ in the file system and returns the path if a
73
- # matching file is found. If no matching file is found, +nil+ is returned. The result is
74
- # cached, meaning that the filesystem lookup for a specific path will only happen once.
75
- #
76
- # @param [TemplateParams] template_params
77
- # @return [String|nil]
78
- def get_template(template_params)
79
- klass = self.class
80
- path = klass.get_template_path(template_params.controller)
81
- template = template_params.cache.ensure_in_cache(path, template_params)
82
- klass.resolve_template(path, template, template_params)
83
- end
84
-
85
- def self.calculate_path(path, template_params)
86
- type, controller, base_dir = template_params.to_a
87
- default_template = controller.settings.fetch("default_#{type}".to_sym)
88
- template =
89
- TemplateLocator.lookup_template_with_default(
90
- Utils.fs_path(base_dir, path), default_template
91
- )
92
- ::Racket::Application.inform_dev(
93
- "Using #{type} #{template.inspect} for #{controller.class}.#{controller.racket.action}."
94
- )
95
- template
96
- end
97
-
98
- def self.resolve_template(path, template, template_params)
99
- return template unless template.is_a?(Proc)
100
- _, controller, base_dir = template_params.to_a
101
- lookup_template(
102
- Utils.fs_path(
103
- Utils.fs_path(base_dir, path).dirname,
104
- call_template_proc(template, controller)
105
- )
106
- )
107
- end
108
-
109
- # Calls a template proc. Depending on how many parameters the template proc takes, different
110
- # types of information will be passed to the proc.
111
- # If the proc takes zero parameters, no information will be passed.
112
- # If the proc takes one parameter, it will contain the current action.
113
- # If the proc takes two parameters, they will contain the current action and the current
114
- # params.
115
- # If the proc takes three parameters, they will contain the current action, the current
116
- # params and the current request.
117
- #
118
- # @param [Proc] proc
119
- # @param [Racket::Controller] controller
120
- # @return [String]
121
- def self.call_template_proc(proc, controller)
122
- racket = controller.racket
123
- proc_args = [racket.action, racket.params, controller.request].slice(0...proc.arity)
124
- proc.call(*proc_args).to_s
125
- end
126
-
127
- # Returns the "url path" that should be used when searching for templates.
128
- #
129
- # @param [Racket::Controller] controller
130
- # @return [String]
131
- def self.get_template_path(controller)
132
- template_path =
133
- [::Racket::Application.get_route(controller.class), controller.racket.action].join('/')
134
- template_path = template_path[1..-1] if template_path.start_with?('//')
135
- template_path
136
- end
137
-
138
- # Locates a file in the filesystem matching an URL path. If there exists a matching file,
139
- # the path to it is returned. If there is no matching file, +nil+ is returned.
140
- # @param [Pathname] path
141
- # @return [Pathname|nil]
142
- def self.lookup_template(path)
143
- Utils.first_matching_path(*Utils.extract_dir_and_glob(path))
144
- end
145
-
146
- # Locates a file in the filesystem matching an URL path. If there exists a matching file,
147
- # the path to it is returned. If there is no matching file and +default_template+ is a
148
- # String or a Symbol, another lookup will be performed using +default_template+. If
149
- # +default_template+ is a Proc or nil, +default_template+ will be used as is instead.
150
- #
151
- # @param [Pathname] path
152
- # @param [String|Symbol|Proc|nil] default_template
153
- # @return [String|Proc|nil]
154
- def self.lookup_template_with_default(path, default_template)
155
- template = lookup_template(path)
156
- unless template
157
- if default_template.is_a?(String) || default_template.is_a?(Symbol)
158
- # Strings and symbols can be lookup up in the file system...
159
- template = lookup_template(Utils.fs_path(path.dirname, default_template))
160
- else
161
- # ...but not nils/procs!
162
- template = default_template
163
- end
164
- end
165
- template
30
+ # Extracts what template settings to use based on context and incoming parameters.
31
+ #
32
+ # @param [Object] context
33
+ # @param [Hash] template_settings
34
+ # @return [Hash]
35
+ def self.extract_template_settings(context, template_settings)
36
+ if context.respond_to?(:view_settings) && !template_settings
37
+ context.view_settings
38
+ elsif template_settings
39
+ template_settings
40
+ else
41
+ {}
166
42
  end
167
43
  end
168
-
169
- # Class responsible for rendering a controller/view/layout combination.
170
- class ViewRenderer
171
- # Renders a page using the provided controller/view and layout combination and returns an
172
- # response array that can be sent to the client.
173
- #
174
- # @param [Racket::Controller] controller
175
- # @param [String] view
176
- # @param [String] layout
177
- # @return [Array]
178
- def self.render(controller, view, layout)
179
- send_response(
180
- controller.response,
181
- view ? render_template(controller, view, layout) : controller.racket.action_result
182
- )
183
- end
184
-
185
- # Renders a template/layout combo using Tilt and returns it as a string.
186
- #
187
- # @param [Racket::Controller] controller
188
- # @param [String] view
189
- # @param [String|nil] layout
190
- # @return [String]
191
- def self.render_template(controller, view, layout)
192
- output = Tilt.new(view).render(controller)
193
- output = Tilt.new(layout).render(controller) { output } if layout
194
- output
195
- end
196
-
197
- # Sends response to client.
198
- #
199
- # @param [Racket::Response] response
200
- # @param [String] output
201
- # @return nil
202
- def self.send_response(response, output)
203
- response.write(output)
204
- response.finish
205
- end
206
-
207
- private_class_method :render_template, :send_response
208
- end
209
-
210
- # Struct for holding template data.
211
- TemplateParams = Struct.new(:type, :controller, :base_dir, :cache)
212
44
  end
213
45
  end
214
46
  end