racket-mvc 0.2.2 → 0.3.0

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.
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