racket-mvc 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 105b08877782c44a513558312f3e9b6af7daef75
4
- data.tar.gz: 4ccd48feacc8788667f1ea5ab580a3cdb17cbdb0
3
+ metadata.gz: 02938217c8513aa37d2e87dbc1232d85be25b94a
4
+ data.tar.gz: e2686cd3a87d07793b886e48428b277bd3ae1639
5
5
  SHA512:
6
- metadata.gz: 7275ee4175f164a783c9f827ddaee45f68b949ac4977c3637f6ff80e6f7662d03f0985926872d8fa68b68d4f1fa91a34c8aca3d2e3c8383cdbbdb9f3f4d45bac
7
- data.tar.gz: 6795e2bfcca8c5671df6e65012fc60ec6ab374458d074c69f91451f9d5fde72de257530c9c32cf0e9336b16f18bc20417a2f6339edeefd09dbba91f62c5a3ab8
6
+ metadata.gz: a76232aecaf2a67c3062713204dca768b1cbc6307e70cb6eaa3fa90d56e0c814c1cace2f756071294b09f1f28375c46e81e93db5842151e9d0e44a5489b74d61
7
+ data.tar.gz: 509beaf22372ace6bd8cc85739c8524006cc03790bf9a49279991c89d4991514a7e176626904ce9367beba87c72f72ed51702026f99357d0e042b3747804d9ad
data/lib/racket.rb CHANGED
@@ -26,6 +26,8 @@ require_relative 'racket/request.rb'
26
26
  require_relative 'racket/response.rb'
27
27
  require_relative 'racket/router.rb'
28
28
  require_relative 'racket/session.rb'
29
+ require_relative 'racket/settings/application.rb'
30
+ require_relative 'racket/settings/controller.rb'
29
31
  require_relative 'racket/view_manager.rb'
30
32
  require_relative 'racket/utils.rb'
31
33
 
@@ -16,28 +16,34 @@
16
16
  # You should have received a copy of the GNU Affero General Public License
17
17
  # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
18
 
19
- require 'logger'
20
-
21
19
  module Racket
22
20
  # Racket main application class.
23
21
  class Application
24
- @options = nil
22
+ @settings = nil
23
+
24
+ class << self
25
+ attr_reader :router, :settings
26
+ end
25
27
 
26
28
  # Returns the internal application object. When called for the first time this method will use
27
- # Rack::Builder to build
29
+ # Rack::Builder to construct the application.
28
30
  #
29
31
  # @return [Rack::Builder]
30
32
  def self.application
31
33
  return @application if @application
32
- @options[:middleware].unshift(@options[:session_handler]) if @options[:session_handler]
33
- @options[:middleware].unshift([Rack::ContentType, @options[:default_content_type]]) if
34
- @options[:default_content_type]
35
- @options[:middleware].unshift([Rack::ShowExceptions]) if dev_mode?
34
+ load_middleware
35
+ build_application
36
+ end
37
+
38
+ # Builds an application from a Rack::Builder object.
39
+ #
40
+ # @return [Rack::Builder]
41
+ def self.build_application
36
42
  instance = self
37
43
  @application = Rack::Builder.new do
38
- instance.options[:middleware].each do |middleware|
44
+ instance.settings.middleware.each do |middleware|
39
45
  klass, opts = middleware
40
- instance.inform_dev("Loading middleware #{klass} with options #{opts.inspect}.")
46
+ instance.inform_dev("Loading middleware #{klass} with settings #{opts.inspect}.")
41
47
  use(*middleware)
42
48
  end
43
49
  run lambda { |env|
@@ -56,42 +62,11 @@ module Racket
56
62
  application.call(env.dup)
57
63
  end
58
64
 
59
- # Returns a list of default options for Racket::Application.
60
- #
61
- # @return [Hash]
62
- def self.default_options
63
- root_dir = Utils.build_path(Dir.pwd)
64
- {
65
- controller_dir: Utils.build_path(root_dir, 'controllers'),
66
- default_action: :index,
67
- default_content_type: 'text/html',
68
- default_controller_helpers: [:routing, :view],
69
- default_layout: nil,
70
- default_view: nil,
71
- helper_dir: Utils.build_path(root_dir, 'helpers'),
72
- layout_dir: Utils.build_path(root_dir, 'layouts'),
73
- logger: Logger.new($stdout),
74
- middleware: [],
75
- mode: :live,
76
- public_dir: Utils.build_path(root_dir, 'public'),
77
- root_dir: root_dir,
78
- session_handler: [
79
- Rack::Session::Cookie,
80
- {
81
- key: 'racket.session',
82
- old_secret: SecureRandom.hex(16),
83
- secret: SecureRandom.hex(16)
84
- }
85
- ],
86
- view_dir: Utils.build_path(root_dir, 'views')
87
- }
88
- end
89
-
90
65
  # Returns whether the application runs in dev mode.
91
66
  #
92
67
  # @return [true|false]
93
68
  def self.dev_mode?
94
- @options[:mode] == :dev
69
+ @settings.mode == :dev
95
70
  end
96
71
 
97
72
  # Returns a route to the specified controller/action/parameter combination.
@@ -104,7 +79,7 @@ module Racket
104
79
  @router.get_route(controller, action, params)
105
80
  end
106
81
 
107
- # Initializes a new Racket::Application object with default options.
82
+ # Initializes a new Racket::Application object with default settings.
108
83
  #
109
84
  # @param [true|false] reboot
110
85
  # @return [Class]
@@ -112,22 +87,13 @@ module Racket
112
87
  init({}, reboot)
113
88
  end
114
89
 
115
- # Expands all paths defined in the application, but only if it is set to something usable.
116
- #
117
- # @return [nil]
118
- def self.expand_paths
119
- [:controller_dir, :helper_dir, :layout_dir, :public_dir, :view_dir].each do |dir|
120
- @options[dir] = Utils.build_path(@options[dir]) if @options[dir]
121
- end && nil
122
- end
123
-
124
90
  # Writes a message to the logger if there is one present.
125
91
  #
126
92
  # @param [String] message
127
93
  # @param [Symbol] level
128
94
  # @return nil
129
95
  def self.inform(message, level)
130
- (@options[:logger].send(level, message) if @options[:logger]) && nil
96
+ (@settings.logger.send(level, message) if @settings.logger) && nil
131
97
  end
132
98
 
133
99
  # Sends a message to the logger.
@@ -150,14 +116,13 @@ module Racket
150
116
 
151
117
  # Initializes the Racket application.
152
118
  #
153
- # @param [Hash] options
119
+ # @param [Hash] settings
154
120
  # @param [true|false] reboot
155
121
  # @return [Class]
156
- def self.init(options, reboot)
122
+ def self.init(settings, reboot)
157
123
  instance_variables.each { |ivar| instance_variable_set(ivar, nil) } if reboot
158
- fail 'Application has already been initialized!' if @options
159
- @options = default_options.merge(options)
160
- expand_paths
124
+ fail 'Application has already been initialized!' if @settings
125
+ @settings = Settings::Application.new(settings)
161
126
  setup_static_server
162
127
  reload
163
128
  self
@@ -168,32 +133,33 @@ module Racket
168
133
  # @return [nil]
169
134
  def self.load_controllers
170
135
  inform_dev('Loading controllers.')
171
- @options[:last_added_controller] = []
136
+ @settings.store(:last_added_controller, [])
172
137
  @controller = nil
173
- Dir.chdir(@options[:controller_dir]) do
138
+ Dir.chdir(@settings.controller_dir) do
174
139
  files = Pathname.glob(File.join('**', '*.rb')).map!(&:to_s)
175
140
  # Sort by longest path so that the longer paths gets matched first
176
141
  # HttpRouter claims to be doing this already, but this "hack" is needed in order
177
142
  # for the router to work.
178
- files.sort! do |a, b|
179
- b.split('/').length <=> a.split('/').length
180
- end
143
+ files.sort! { |a, b| b.split('/').length <=> a.split('/').length }
181
144
  files.each do |file|
182
145
  ::Kernel.require File.expand_path(file)
183
146
  path = "/#{File.dirname(file)}"
184
147
  path = '' if path == '/.'
185
- @router.map(path, @options[:last_added_controller].pop)
148
+ @router.map(path, @settings.fetch(:last_added_controller).pop)
186
149
  end
187
150
  end
188
- @options.delete(:last_added_controller)
151
+ @settings.delete(:last_added_controller)
189
152
  inform_dev('Done loading controllers.') && nil
190
153
  end
191
154
 
192
- # Returns options for the currently running Racket::Application.
155
+ # Loads some middleware (based on settings).
193
156
  #
194
- # @return [Hash]
195
- def self.options
196
- @options
157
+ # @return nil
158
+ def self.load_middleware
159
+ @settings.middleware.unshift(@settings.session_handler) if @settings.session_handler
160
+ @settings.middleware.unshift([Rack::ContentType, @settings.default_content_type]) if
161
+ @settings.default_content_type
162
+ (@settings.middleware.unshift([Rack::ShowExceptions]) if dev_mode?) && nil
197
163
  end
198
164
 
199
165
  # Reloads the application, making any changes to the controller configuration visible
@@ -213,13 +179,6 @@ module Racket
213
179
  (::Kernel.require Utils.build_path(*args)) && nil
214
180
  end
215
181
 
216
- # Returns the router associated with the currenntly running Racket::Application.
217
- #
218
- # @return [Racket::Router]
219
- def self.router
220
- @router
221
- end
222
-
223
182
  # Serves a static file (if Racket is configured to serve static files).
224
183
  #
225
184
  # @param [Hash] env Rack environment
@@ -242,28 +201,28 @@ module Racket
242
201
  # @return [nil]
243
202
  def self.setup_static_server
244
203
  @static_server = nil
245
- return nil unless (public_dir = @options[:public_dir]) && Utils.dir_readable?(public_dir)
204
+ return nil unless (public_dir = @settings.public_dir) && Utils.dir_readable?(public_dir)
246
205
  inform_dev("Setting up static server to serve files from #{public_dir}.")
247
206
  (@static_server = Rack::File.new(public_dir)) && nil
248
207
  end
249
208
 
250
- # Initializes a new Racket::Application object with options specified by +options+.
209
+ # Initializes a new Racket::Application object with settings specified by +settings+.
251
210
  #
252
- # @param [Hash] options
211
+ # @param [Hash] settings
253
212
  # @param [true|false] reboot
254
213
  # @return [Class]
255
- def self.using(options, reboot = false)
256
- init(options, reboot)
214
+ def self.using(settings, reboot = false)
215
+ init(settings, reboot)
257
216
  end
258
217
 
259
218
  # Returns the view cache of the currently running application.
260
219
  #
261
220
  # @return [Racket::ViewManager]
262
221
  def self.view_manager
263
- @view_manager ||= ViewManager.new(@options[:layout_dir], @options[:view_dir])
222
+ @view_manager ||= ViewManager.new(@settings.layout_dir, @settings.view_dir)
264
223
  end
265
224
 
266
- private_class_method :application, :default_options, :expand_paths, :inform, :init,
267
- :load_controllers, :setup_routes, :setup_static_server
225
+ private_class_method :application, :build_application, :inform, :init, :load_controllers,
226
+ :load_middleware, :setup_routes, :setup_static_server
268
227
  end
269
228
  end
@@ -20,7 +20,7 @@ module Racket
20
20
  # Base controller class. Your controllers should inherit this class.
21
21
  class Controller
22
22
  def self.__load_helpers(helpers)
23
- helper_dir = Application.options.fetch(:helper_dir, nil)
23
+ helper_dir = Application.settings.helper_dir
24
24
  helper_modules = {}
25
25
  helpers.each do |helper|
26
26
  helper_module = helper.to_s.split('_').collect(&:capitalize).join.to_sym
@@ -51,9 +51,9 @@ module Racket
51
51
  key = "#{type}_hooks".to_sym
52
52
  meths = public_instance_methods(false)
53
53
  meths &= methods.map(&:to_sym) unless methods.empty?
54
- hooks = get_option(key) || {}
54
+ hooks = settings.fetch(key, {})
55
55
  meths.each { |meth| hooks[meth] = blk }
56
- set_option(key, hooks)
56
+ setting(key, hooks)
57
57
  nil
58
58
  end
59
59
 
@@ -78,62 +78,55 @@ module Racket
78
78
  end
79
79
 
80
80
  # Adds one or more helpers to the controller. All controllers get some default helpers
81
- # (see Application.default_options), but if you have your own helpers you want to load this
81
+ # (:routing and :view by default), but if you have your own helpers you want to load this
82
82
  # is the preferred method.
83
83
  #
84
84
  # By default Racket will look for your helpers in the helpers directory, but you can specify
85
- # another location by setting the helper_dir option.
85
+ # another location by changing the helper_dir setting.
86
86
  #
87
87
  # @param [Array] helpers An array of symbols representing classes living in the Racket::Helpers
88
88
  # namespace.
89
89
  def self.helper(*helpers)
90
90
  helper_modules = {}
91
- existing_helpers = get_option(:helpers)
91
+ existing_helpers = settings.fetch(:helpers)
92
92
  if existing_helpers.nil?
93
93
  # No helpers has been loaded yet. Load the default helpers.
94
- existing_helpers = Application.options.fetch(:default_controller_helpers, [])
94
+ existing_helpers = Application.settings.default_controller_helpers
95
95
  helper_modules.merge!(__load_helpers(existing_helpers))
96
96
  end
97
97
  # Load new helpers
98
98
  helpers.map!(&:to_sym)
99
99
  helpers.reject! { |helper| helper_modules.key?(helper) }
100
100
  helper_modules.merge!(__load_helpers(helpers))
101
- set_option(:helpers, helper_modules)
101
+ setting(:helpers, helper_modules)
102
102
  end
103
103
 
104
104
  # :nodoc:
105
105
  def self.inherited(klass)
106
- Application.options[:last_added_controller].push(klass)
106
+ Application.settings.fetch(:last_added_controller).push(klass)
107
107
  end
108
108
 
109
- # Returns an option for the current controller class or any of the controller classes
110
- # it is inheriting from.
109
+ # Returns the settings associated with the current controller class.
111
110
  #
112
- # @param [Symbol] key The option to retrieve
113
- # @return [Object]
114
- def self.get_option(key)
115
- @options ||= {}
116
- return @options[key] if @options.key?(key)
117
- # We are running out of controller options, do one final lookup in Application.options
118
- return Application.options.fetch(key, nil) if superclass == Controller
119
- superclass.get_option(key)
111
+ # @return [Racket::Settings::Controller]
112
+ def self.settings
113
+ @settings ||= Settings::Controller.new(self)
120
114
  end
121
115
 
122
- # Sets an option for the current controller class.
116
+ # Creates/updates a setting for the current controller class.
123
117
  #
124
118
  # @param [Symbol] key
125
119
  # @param [Object] value
126
- def self.set_option(key, value)
127
- @options ||= {}
128
- (@options[key] = value) && nil
120
+ # @return [nil]
121
+ def self.setting(key, value)
122
+ settings.store(key, value)
129
123
  end
130
124
 
131
- # Returns an option from the current controller class.
125
+ # Returns the settings for a controller instance.
132
126
  #
133
- # @param [Symbol] key
134
- # @return
135
- def controller_option(key)
136
- self.class.get_option(key)
127
+ # @return [Racket::Settings::Controller]
128
+ def settings
129
+ self.class.settings
137
130
  end
138
131
 
139
132
  # Redirects the client. After hooks are run.
@@ -196,7 +189,7 @@ module Racket
196
189
  end
197
190
 
198
191
  def __run_hook(type)
199
- hooks = controller_option("#{type}_hooks".to_sym) || {}
192
+ hooks = settings.fetch("#{type}_hooks".to_sym, {})
200
193
  blk = hooks.fetch(racket.action, nil)
201
194
  (instance_eval(&blk) if blk) && nil
202
195
  end
@@ -34,10 +34,10 @@ module Racket
34
34
  # @param [Array] params Parameters sent to the action
35
35
  # @return [Module] A module encapsulating all state relating to the current request
36
36
  def self.init(env, klass, action, params)
37
- klass.helper if klass.get_option(:helpers).nil? # Makes sure default helpers are loaded.
37
+ klass.helper if klass.settings.fetch(:helpers).nil? # Makes sure default helpers are loaded.
38
38
  properties = init_properties(action, params, env)
39
39
  Module.new do
40
- klass.get_option(:helpers).each_value { |helper| include helper }
40
+ klass.settings.fetch(:helpers).each_value { |helper| include helper }
41
41
  properties.each_pair { |key, value| define_method(key) { value } }
42
42
  end
43
43
  end
data/lib/racket/router.rb CHANGED
@@ -94,34 +94,61 @@ module Racket
94
94
  # @return [Array] A Rack response triplet
95
95
  def route(env)
96
96
  catch :response do # Catches early exits from Controller.respond.
97
- # Find controller in map
98
- # If controller exists, call it
99
- # Otherwise, send a 404
100
- matching_routes = @router.recognize(env)
101
-
102
- # Exit early if no controller is responsible for the route
103
- return render_error(404) if matching_routes.first.nil?
104
-
105
- # Some controller is claiming to be responsible for the route
106
- target_klass = matching_routes.first.first.route.dest
107
- params = matching_routes.first.first.param_values.first.reject(&:empty?)
108
- action = params.empty? ? target_klass.get_option(:default_action) : params.shift.to_sym
109
-
110
- # Check if action is available on target
111
- return render_error(404) unless @action_cache[target_klass].include?(action)
97
+ # Ensure that that a controller will respond to the request. If not, send a 404.
98
+ return render_error(404) if (target_info = target_info(env)).nil?
99
+ target_klass, params, action = target_info
112
100
 
113
101
  # Rewrite PATH_INFO to reflect that we split out the parameters
114
- env['PATH_INFO'] = env['PATH_INFO']
115
- .split('/')[0...-params.count]
116
- .join('/') unless params.empty?
102
+ update_path_info(env, params.length)
117
103
 
118
104
  # Initialize and render target
119
105
  target = target_klass.new
120
106
  target.extend(Current.init(env, target_klass, action, params))
121
107
  target.__run
122
108
  end
123
- rescue => err
124
- render_error(500, err)
109
+ rescue => err
110
+ render_error(500, err)
111
+ end
112
+
113
+ private
114
+
115
+ # Returns information about the target of the request. If no valid target can be found, +nil+
116
+ # is returned.
117
+ #
118
+ # @param [Hash] env
119
+ # @return [Array|nil]
120
+ def target_info(env)
121
+ matching_routes = @router.recognize(env)
122
+ # Exit early if no controller is responsible for the route
123
+ return nil if matching_routes.first.nil?
124
+ # Some controller is claiming to be responsible for the route
125
+ result = extract_target(matching_routes)
126
+ # Exit early if action is not available on target
127
+ return nil unless @action_cache[result.first].include?(result.last)
128
+ result
129
+ end
130
+
131
+ # Extracts the target class, target params and target action from a list of valid routes.
132
+ #
133
+ # @param [Array] routes
134
+ # @return [Array]
135
+ def extract_target(routes)
136
+ target_klass = routes.first.first.route.dest
137
+ params = routes.first.first.param_values.first.reject(&:empty?)
138
+ action = params.empty? ? target_klass.settings.fetch(:default_action) : params.shift.to_sym
139
+ [target_klass, params, action]
140
+ end
141
+
142
+ # Updates the PATH_INFO environment variable.
143
+ #
144
+ # @param [Hash] env
145
+ # @param [Fixnum] num_params
146
+ # @return [nil]
147
+ def update_path_info(env, num_params)
148
+ env['PATH_INFO'] = env['PATH_INFO']
149
+ .split('/')[0...-num_params]
150
+ .join('/') unless num_params.zero?
151
+ nil
125
152
  end
126
153
  end
127
154
  end
@@ -0,0 +1,74 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'logger'
20
+
21
+ require_relative 'base.rb'
22
+
23
+ module Racket
24
+ module Settings
25
+ # Class for storing application settings.
26
+ class Application < Base
27
+ setting(:default_action, :index)
28
+ setting(:default_content_type, 'text/html')
29
+ setting(:default_controller_helpers, [:routing, :view])
30
+ setting(:default_layout, nil)
31
+ setting(:default_view, nil)
32
+ setting(:logger, Logger.new($stdout))
33
+ setting(:middleware, [])
34
+ setting(:mode, :live)
35
+ setting(
36
+ :session_handler,
37
+ [
38
+ Rack::Session::Cookie,
39
+ {
40
+ key: 'racket.session',
41
+ old_secret: SecureRandom.hex(16),
42
+ secret: SecureRandom.hex(16)
43
+ }
44
+ ]
45
+ )
46
+ setting(:root_dir, nil) # Will be set automatically by constructor.
47
+
48
+ def initialize(defaults = {})
49
+ defaults[:root_dir] = Dir.pwd unless defaults.key?(:root_dir)
50
+ super(defaults)
51
+ end
52
+
53
+ # Creates a directory setting with a default value.
54
+ #
55
+ # @param [Symbol] symbol
56
+ # @param [String] directory
57
+ # @return [nil]
58
+ def self.directory_setting(symbol, directory)
59
+ ivar = "@#{symbol}".to_sym
60
+ define_method symbol do
61
+ instance_variable_set(ivar, directory) unless instance_variables.include?(ivar)
62
+ Utils.build_path(instance_variable_get(ivar))
63
+ end
64
+ attr_writer(symbol) && nil
65
+ end
66
+
67
+ directory_setting(:controller_dir, 'controllers')
68
+ directory_setting(:helper_dir, 'helpers')
69
+ directory_setting(:layout_dir, 'layouts')
70
+ directory_setting(:public_dir, 'public')
71
+ directory_setting(:view_dir, 'views')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,83 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module Racket
20
+ # Module for handling Racket settings.
21
+ module Settings
22
+ # Base class for settings.
23
+ class Base
24
+ def initialize(defaults = {})
25
+ @custom = {}
26
+ defaults.each_pair do |key, value|
27
+ meth = "#{key}=".to_sym
28
+ if respond_to?(meth) then send(meth, value)
29
+ else @custom[key] = value
30
+ end
31
+ end
32
+ end
33
+
34
+ # Deletes a custom setting associated with the application.
35
+ #
36
+ # @param [Symbol] key
37
+ # @return [nil]
38
+ def delete(key)
39
+ fail ArgumentErrpr,
40
+ "Cannot delete standard setting #{key}" if respond_to?(key.to_sym)
41
+ @custom.delete(key) && nil
42
+ end
43
+
44
+ # Returns a settings value associated with the application. Both standard and custom
45
+ # settings are searched. If the key cannot be found, a default value is returned.
46
+ #
47
+ # @param [Symbol] key
48
+ # @param [Object] default
49
+ # @return [Object]
50
+ def fetch(key, default = nil)
51
+ meth = key.to_sym
52
+ return send(meth) if respond_to?(meth)
53
+ @custom.fetch(key, default)
54
+ end
55
+
56
+ # Sets/updates a custom setting in the application.
57
+ #
58
+ # @param [Symbol] key
59
+ # @param [Object] value
60
+ # @return [nil]
61
+ def store(key, value)
62
+ fail ArgumentError,
63
+ "Cannot overwrite standard setting #{key}" if respond_to?("#{key}=".to_sym)
64
+ (@custom[key] = value) && nil
65
+ end
66
+
67
+ # Creates a setting with a default value.
68
+ #
69
+ # @param [Symbol] symbol
70
+ # @param [Object] default
71
+ # @param [true|false] writable
72
+ # @return [nil]
73
+ def self.setting(symbol, default = nil, writable = true)
74
+ ivar = "@#{symbol}".to_sym
75
+ define_method symbol do
76
+ instance_variable_set(ivar, default) unless instance_variables.include?(ivar)
77
+ instance_variable_get(ivar)
78
+ end
79
+ (attr_writer(symbol) if writable) && nil
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,44 @@
1
+ # Racket - The noisy Rack MVC framework
2
+ # Copyright (C) 2015 Lars Olsson <lasso@lassoweb.se>
3
+ #
4
+ # This file is part of Racket.
5
+ #
6
+ # Racket is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # Racket is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with Racket. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require_relative 'base.rb'
20
+
21
+ module Racket
22
+ module Settings
23
+ # Class for storing controller settings.
24
+ # This settings class will lookup settings further up in the inheritance chain and will use
25
+ # the application settings as a final fallback.
26
+ class Controller < Base
27
+ def initialize(owner, defaults = {})
28
+ super(defaults)
29
+ @owner = owner
30
+ end
31
+
32
+ # Fetches settings from the current object. If the setting cannot be found in the Current
33
+ # object, the controller superklass will be queried. If all controller classes in the
34
+ # inheritance chain has been queried, the Application settings will be used as a final
35
+ # fallback.
36
+ def fetch(key, default = nil)
37
+ return @custom[key] if @custom.key?(key)
38
+ return ::Racket::Application.settings.fetch(key, default) if
39
+ @owner.superclass == ::Racket::Controller
40
+ @owner.superclass.settings.fetch(key, default)
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/racket/utils.rb CHANGED
@@ -63,7 +63,7 @@ module Racket
63
63
  else
64
64
  args.map!(&:to_s)
65
65
  path = Pathname.new(args.shift)
66
- path = Pathname.new(Application.options[:root_dir]).join(path) if path.relative?
66
+ path = Pathname.new(Application.settings.root_dir).join(path) if path.relative?
67
67
  args.each do |arg|
68
68
  path_part = Pathname.new(arg)
69
69
  next unless path_part.relative?
@@ -23,9 +23,9 @@ module Racket
23
23
  # Major version
24
24
  MAJOR = 0
25
25
  # Minor version
26
- MINOR = 2
26
+ MINOR = 3
27
27
  # Teeny version
28
- TEENY = 2
28
+ TEENY = 0
29
29
  # Is it a prerelease?
30
30
  PRERELEASE = false
31
31
 
@@ -38,26 +38,15 @@ module Racket
38
38
  # @return [Hash]
39
39
  def render(controller)
40
40
  template_path = get_template_path(controller)
41
- view = get_view(template_path, controller)
42
- if view
43
- output = Tilt.new(view).render(controller)
44
- layout = get_layout(template_path, controller)
45
- output = Tilt.new(layout).render(controller) { output } if layout
46
- else
47
- output = controller.racket.action_result
48
- end
41
+ view = get_template(template_path, controller, :view)
42
+ layout = view ? get_template(template_path, controller, :layout) : nil
43
+ output = view ? render_template(controller, view, layout) : controller.racket.action_result
49
44
  controller.response.write(output)
50
45
  controller.response.finish
51
46
  end
52
47
 
53
48
  private
54
49
 
55
- def get_template_path(controller)
56
- template_path = [Application.get_route(controller.class), controller.racket.action].join('/')
57
- template_path = template_path[1..-1] if template_path.start_with?('//')
58
- template_path
59
- end
60
-
61
50
  # Calls a template proc. Depending on how many parameters the template proc takes, different
62
51
  # types of information will be passed to the proc.
63
52
  # If the proc takes zero parameters, no information will be passed.
@@ -82,56 +71,40 @@ module Racket
82
71
  # @param [String] path
83
72
  # @param [Racket::Controller] controller
84
73
  # @param [Symbol] type
74
+ # @return [String|Proc|nil]
85
75
  def ensure_in_cache(path, controller, type)
86
76
  store = instance_variable_get("@#{type}_cache".to_sym)
87
77
  return store[path] if store.key?(path)
88
- base_dir = instance_variable_get("@#{type}_base_dir".to_sym)
89
- default_template = controller.controller_option("default_#{type}".to_sym)
90
- template = lookup_template(base_dir, path)
91
- template =
92
- lookup_default_template(base_dir, File.dirname(path), default_template) unless template
93
- Application.inform_dev(
94
- "Using #{type} #{template.inspect} for #{controller.class}.#{controller.racket.action}."
95
- )
96
- store[path] = template
78
+ store_in_cache(store, path, controller, type)
97
79
  end
98
80
 
99
- # Tries to locate a layout matching +path+ in the file system and returns the path if a
81
+ # Tries to locate a template matching +path+ in the file system and returns the path if a
100
82
  # matching file is found. If no matching file is found, +nil+ is returned. The result is cached,
101
83
  # meaning that the filesystem lookup for a specific path will only happen once.
102
84
  #
103
85
  # @param [String] path
104
86
  # @param [Racket::Controller] controller
87
+ # @param [Symbol] type
105
88
  # @return [String|nil]
106
- def get_layout(path, controller)
107
- layout = ensure_in_cache(path, controller, :layout)
108
- if layout.is_a?(Proc)
109
- layout =
110
- lookup_template(
111
- @layout_base_dir,
112
- [File.dirname(path), call_template_proc(layout, controller)].join('/')
113
- )
114
- end
115
- layout
89
+ def get_template(path, controller, type)
90
+ template = ensure_in_cache(path, controller, type)
91
+ # If template is a Proc, call it
92
+ template =
93
+ lookup_template(
94
+ instance_variable_get("@#{type}_base_dir".to_sym),
95
+ [File.dirname(path), call_template_proc(template, controller)].join('/')
96
+ ) if template.is_a?(Proc)
97
+ template
116
98
  end
117
99
 
118
- # Tries to locate a view matching +path+ in the file system and returns the path if a
119
- # matching file is found. If no matching file is found, +nil+ is returned. The result is cached,
120
- # meaning that the filesystem lookup for a specific path will only happen once.
100
+ # Returns the "url path" that should be used when searching for templates.
121
101
  #
122
- # @param [String] path
123
102
  # @param [Racket::Controller] controller
124
- # @return [String|nil]
125
- def get_view(path, controller)
126
- view = ensure_in_cache(path, controller, :view)
127
- if view.is_a?(Proc)
128
- view =
129
- lookup_template(
130
- @view_base_dir,
131
- [File.dirname(path), call_template_proc(view, controller)].join('/')
132
- )
133
- end
134
- view
103
+ # @return [String]
104
+ def get_template_path(controller)
105
+ template_path = [Application.get_route(controller.class), controller.racket.action].join('/')
106
+ template_path = template_path[1..-1] if template_path.start_with?('//')
107
+ template_path
135
108
  end
136
109
 
137
110
  def lookup_default_template(base_path, path, default)
@@ -159,5 +132,36 @@ module Racket
159
132
  Utils.file_readable?(final_path) ? final_path : nil
160
133
  end
161
134
  end
135
+
136
+ # Renders a template/layout combo using Tilt and returns it as a string.
137
+ #
138
+ # @param [Racket::Controller] controller
139
+ # @param [String] view
140
+ # @param [String|nil] layout
141
+ # @return [String]
142
+ def render_template(controller, view, layout)
143
+ output = Tilt.new(view).render(controller)
144
+ output = Tilt.new(layout).render(controller) { output } if layout
145
+ output
146
+ end
147
+
148
+ # Stores the location of a template (not its contents) in the cache.
149
+ #
150
+ # @param [Object] store Where to store the location
151
+ # @param [String] path
152
+ # @param [Racket::Controller] controller
153
+ # @param [Symbol] type
154
+ # @return [String|Proc|nil]
155
+ def store_in_cache(store, path, controller, type)
156
+ base_dir = instance_variable_get("@#{type}_base_dir".to_sym)
157
+ default_template = controller.settings.fetch("default_#{type}".to_sym)
158
+ template = lookup_template(base_dir, path)
159
+ template =
160
+ lookup_default_template(base_dir, File.dirname(path), default_template) unless template
161
+ Application.inform_dev(
162
+ "Using #{type} #{template.inspect} for #{controller.class}.#{controller.racket.action}."
163
+ )
164
+ store[path] = template
165
+ end
162
166
  end
163
167
  end
data/spec/_custom.rb CHANGED
@@ -2,17 +2,23 @@ describe 'A custom Racket test Application' do
2
2
  extend Rack::Test::Methods
3
3
  def app
4
4
  @app ||= Racket::Application.using(
5
- { default_layout: 'zebra.*', logger: nil, mode: :dev, view_dir: 'templates' },
5
+ {
6
+ default_layout: 'zebra.*',
7
+ logger: nil,
8
+ my_custom_secret: 42,
9
+ mode: :dev,
10
+ view_dir: 'templates'
11
+ },
6
12
  true
7
13
  )
8
14
  end
9
15
 
10
- it 'should set requested options' do
11
- app.options[:default_layout].should.equal('zebra.*')
12
- app.options[:view_dir].should.equal(Racket::Utils.build_path('templates'))
16
+ it 'should set requested settings' do
17
+ app.settings.default_layout.should.equal('zebra.*')
18
+ app.settings.view_dir.should.equal(Racket::Utils.build_path('templates'))
13
19
  end
14
20
 
15
- it 'should be able to get/set options on controller' do
21
+ it 'should be able to get/set settings on controller' do
16
22
  get '/sub3/a_secret_place'
17
23
  last_response.status.should.equal(302)
18
24
  last_response.headers['Location'].should.equal('/sub3/a_secret_place/42')
@@ -118,4 +124,20 @@ describe 'A custom Racket test Application' do
118
124
  last_response.status.should.equal(200)
119
125
  last_response.body.should.equal("LAYOUT: default: BAZ\n\n")
120
126
  end
127
+
128
+ it 'should handle changes to global settings' do
129
+ app.settings.fetch(:my_custom_secret).should.equal(42)
130
+ app.settings.store(:my_custom_secret, '9Lazy9')
131
+ app.settings.fetch(:my_custom_secret).should.equal('9Lazy9')
132
+ app.settings.delete(:my_custom_secret)
133
+ app.settings.fetch(:my_custom_secret).should.equal(nil)
134
+ app.settings.default_content_type.should.equal('text/html')
135
+ app.settings.fetch(:default_content_type).should.equal('text/html')
136
+ app.settings.default_content_type = 'text/plain'
137
+ app.settings.default_content_type.should.equal('text/plain')
138
+ app.settings.fetch(:default_content_type).should.equal('text/plain')
139
+ app.settings.default_content_type = 'text/html'
140
+ app.settings.default_content_type.should.equal('text/html')
141
+ app.settings.fetch(:default_content_type).should.equal('text/html')
142
+ end
121
143
  end
data/spec/_default.rb CHANGED
@@ -101,27 +101,27 @@ describe 'A default Racket test Application' do
101
101
  end
102
102
 
103
103
  it 'should be able to log messages to everybody' do
104
- original_logger = app.options[:logger]
104
+ original_logger = app.settings.logger
105
105
  sio = StringIO.new
106
- app.options[:logger] = Logger.new(sio)
106
+ app.settings.logger = Logger.new(sio)
107
107
  app.inform_all('Informational message')
108
108
  sio.string.should.match(/Informational message/)
109
- app.options[:logger] = original_logger
109
+ app.settings.logger = original_logger
110
110
  end
111
111
 
112
112
  it 'should be able to log messages to developer' do
113
- original_logger = app.options[:logger]
114
- original_mode = app.options[:mode]
113
+ original_logger = app.settings.logger
114
+ original_mode = app.settings.mode
115
115
  sio = StringIO.new
116
- app.options[:logger] = Logger.new(sio)
117
- app.options[:mode] = :live
116
+ app.settings.logger = Logger.new(sio)
117
+ app.settings.mode = :live
118
118
  app.inform_dev('Development message')
119
119
  sio.string.should.be.empty
120
- app.options[:mode] = :dev
120
+ app.settings.mode = :dev
121
121
  app.inform_dev('Hey, listen up!')
122
122
  sio.string.should.match(/Hey, listen up!/)
123
- app.options[:mode] = original_mode
124
- app.options[:logger] = original_logger
123
+ app.settings.mode = original_mode
124
+ app.settings.logger = original_logger
125
125
  end
126
126
 
127
127
  it 'should be able to set and clear session variables' do
@@ -1,13 +1,13 @@
1
1
  # Custom sub controller 3
2
2
  class CustomSubController3 < Racket::Controller
3
- set_option(:top_secret, 42)
3
+ setting(:top_secret, 42)
4
4
 
5
5
  def index
6
6
  "#{self.class}::#{__method__}"
7
7
  end
8
8
 
9
9
  def a_secret_place
10
- redirect(rs(__method__, controller_option(:top_secret)))
10
+ redirect(rs(__method__, settings.fetch(:top_secret)))
11
11
  end
12
12
 
13
13
  def not_so_secret
@@ -1,15 +1,15 @@
1
1
  # Custom sub controller 4
2
2
  class CustomSubController4 < Racket::Controller
3
- set_option :default_layout, -> { 'layout.erb' }
3
+ setting :default_layout, -> { 'layout.erb' }
4
4
 
5
- set_option :default_view,
6
- lambda { |action|
7
- case action
8
- when :foo then 'myfoo.erb'
9
- when :bar then 'mybar.erb'
10
- else 'default.erb'
11
- end
12
- }
5
+ setting :default_view,
6
+ lambda { |action|
7
+ case action
8
+ when :foo then 'myfoo.erb'
9
+ when :bar then 'mybar.erb'
10
+ else 'default.erb'
11
+ end
12
+ }
13
13
 
14
14
  def foo
15
15
  @data = 'FOO'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racket-mvc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lars Olsson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-21 00:00:00.000000000 Z
11
+ date: 2015-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http_router
@@ -156,6 +156,9 @@ files:
156
156
  - lib/racket/response.rb
157
157
  - lib/racket/router.rb
158
158
  - lib/racket/session.rb
159
+ - lib/racket/settings/application.rb
160
+ - lib/racket/settings/base.rb
161
+ - lib/racket/settings/controller.rb
159
162
  - lib/racket/utils.rb
160
163
  - lib/racket/version.rb
161
164
  - lib/racket/view_manager.rb