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 +4 -4
- data/lib/racket.rb +2 -0
- data/lib/racket/application.rb +43 -84
- data/lib/racket/controller.rb +22 -29
- data/lib/racket/current.rb +2 -2
- data/lib/racket/router.rb +47 -20
- data/lib/racket/settings/application.rb +74 -0
- data/lib/racket/settings/base.rb +83 -0
- data/lib/racket/settings/controller.rb +44 -0
- data/lib/racket/utils.rb +1 -1
- data/lib/racket/version.rb +2 -2
- data/lib/racket/view_manager.rb +53 -49
- data/spec/_custom.rb +27 -5
- data/spec/_default.rb +10 -10
- data/spec/test_custom_app/controllers/sub3/custom_sub_controller_3.rb +2 -2
- data/spec/test_custom_app/controllers/sub4/custom_sub_controller_4.rb +9 -9
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 02938217c8513aa37d2e87dbc1232d85be25b94a
|
4
|
+
data.tar.gz: e2686cd3a87d07793b886e48428b277bd3ae1639
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
data/lib/racket/application.rb
CHANGED
@@ -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
|
-
@
|
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
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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.
|
44
|
+
instance.settings.middleware.each do |middleware|
|
39
45
|
klass, opts = middleware
|
40
|
-
instance.inform_dev("Loading middleware #{klass} with
|
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
|
-
@
|
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
|
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
|
-
(@
|
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]
|
119
|
+
# @param [Hash] settings
|
154
120
|
# @param [true|false] reboot
|
155
121
|
# @return [Class]
|
156
|
-
def self.init(
|
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 @
|
159
|
-
@
|
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
|
-
@
|
136
|
+
@settings.store(:last_added_controller, [])
|
172
137
|
@controller = nil
|
173
|
-
Dir.chdir(@
|
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!
|
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, @
|
148
|
+
@router.map(path, @settings.fetch(:last_added_controller).pop)
|
186
149
|
end
|
187
150
|
end
|
188
|
-
@
|
151
|
+
@settings.delete(:last_added_controller)
|
189
152
|
inform_dev('Done loading controllers.') && nil
|
190
153
|
end
|
191
154
|
|
192
|
-
#
|
155
|
+
# Loads some middleware (based on settings).
|
193
156
|
#
|
194
|
-
# @return
|
195
|
-
def self.
|
196
|
-
@
|
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 = @
|
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
|
209
|
+
# Initializes a new Racket::Application object with settings specified by +settings+.
|
251
210
|
#
|
252
|
-
# @param [Hash]
|
211
|
+
# @param [Hash] settings
|
253
212
|
# @param [true|false] reboot
|
254
213
|
# @return [Class]
|
255
|
-
def self.using(
|
256
|
-
init(
|
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(@
|
222
|
+
@view_manager ||= ViewManager.new(@settings.layout_dir, @settings.view_dir)
|
264
223
|
end
|
265
224
|
|
266
|
-
private_class_method :application, :
|
267
|
-
:
|
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
|
data/lib/racket/controller.rb
CHANGED
@@ -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.
|
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 =
|
54
|
+
hooks = settings.fetch(key, {})
|
55
55
|
meths.each { |meth| hooks[meth] = blk }
|
56
|
-
|
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
|
-
# (
|
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
|
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 =
|
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.
|
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
|
-
|
101
|
+
setting(:helpers, helper_modules)
|
102
102
|
end
|
103
103
|
|
104
104
|
# :nodoc:
|
105
105
|
def self.inherited(klass)
|
106
|
-
Application.
|
106
|
+
Application.settings.fetch(:last_added_controller).push(klass)
|
107
107
|
end
|
108
108
|
|
109
|
-
# Returns
|
110
|
-
# it is inheriting from.
|
109
|
+
# Returns the settings associated with the current controller class.
|
111
110
|
#
|
112
|
-
# @
|
113
|
-
|
114
|
-
|
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
|
-
#
|
116
|
+
# Creates/updates a setting for the current controller class.
|
123
117
|
#
|
124
118
|
# @param [Symbol] key
|
125
119
|
# @param [Object] value
|
126
|
-
|
127
|
-
|
128
|
-
(
|
120
|
+
# @return [nil]
|
121
|
+
def self.setting(key, value)
|
122
|
+
settings.store(key, value)
|
129
123
|
end
|
130
124
|
|
131
|
-
# Returns
|
125
|
+
# Returns the settings for a controller instance.
|
132
126
|
#
|
133
|
-
# @
|
134
|
-
|
135
|
-
|
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 =
|
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
|
data/lib/racket/current.rb
CHANGED
@@ -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.
|
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.
|
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
|
-
#
|
98
|
-
|
99
|
-
|
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
|
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
|
-
|
124
|
-
|
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.
|
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?
|
data/lib/racket/version.rb
CHANGED
data/lib/racket/view_manager.rb
CHANGED
@@ -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 =
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
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
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
#
|
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
|
125
|
-
def
|
126
|
-
|
127
|
-
if
|
128
|
-
|
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
|
-
{
|
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
|
11
|
-
app.
|
12
|
-
app.
|
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
|
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.
|
104
|
+
original_logger = app.settings.logger
|
105
105
|
sio = StringIO.new
|
106
|
-
app.
|
106
|
+
app.settings.logger = Logger.new(sio)
|
107
107
|
app.inform_all('Informational message')
|
108
108
|
sio.string.should.match(/Informational message/)
|
109
|
-
app.
|
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.
|
114
|
-
original_mode = app.
|
113
|
+
original_logger = app.settings.logger
|
114
|
+
original_mode = app.settings.mode
|
115
115
|
sio = StringIO.new
|
116
|
-
app.
|
117
|
-
app.
|
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.
|
120
|
+
app.settings.mode = :dev
|
121
121
|
app.inform_dev('Hey, listen up!')
|
122
122
|
sio.string.should.match(/Hey, listen up!/)
|
123
|
-
app.
|
124
|
-
app.
|
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
|
-
|
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__,
|
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
|
-
|
3
|
+
setting :default_layout, -> { 'layout.erb' }
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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.
|
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-
|
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
|