racket-mvc 0.3.1 → 0.3.2

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: 395ab3d4006c31ae432dfa280c626166fc41a061
4
- data.tar.gz: 0e22dba439da9506e191c0a253f49b36e6f0d050
3
+ metadata.gz: 001d50b469610f255ddd267d79ee2d0a0a2a2968
4
+ data.tar.gz: 04d8c1b0abad0ae5737f3f5cc7a9083aec28bd04
5
5
  SHA512:
6
- metadata.gz: c8cf2e4fdcb7814d204a72ff06f6774088f7903afce071039e75b69c83340767f2f6dd4b91c60bd0a197e89cf78b85dc95b5a33784423a8cfc5a257fdb3d46ac
7
- data.tar.gz: 02af1c5652968eb149b6945d81232eda5b5291e9e79eae9aa1f4f0bb5ef649a22bfbe5c618e8d0d2112731b361229ee3fcba2853bbedad26e4486ef1d4a0f418
6
+ metadata.gz: 20570ca234fddaa3bd24ebd40346d5b4d26cc6332768257b22c3a5d4167adfa6e7a2860abaf74a331018a456b1b457489b1f85a1b109e51c89ceb0917b9503ed
7
+ data.tar.gz: ca1f23f9ea53cc2d0e63b5627ba36118cca99da29093d213d00cec9cab65b9be2f45506bb044c332b040fc710060e0244f9bb591a3c4cc3f3e60bddca3d8f634
data/README.md CHANGED
@@ -40,7 +40,7 @@ If you are not interested in running the tests yourself you could have a look at
40
40
  ## Alright, I want to try using this stuff. Where are the docs?
41
41
  At the moment there is not much documentation available, but I have started working on the [wiki](https://github.com/lasso/racket/wiki).
42
42
 
43
- The code itself is documented using [Yard](http://yardoc.org/). The docs are not generated automatically, you need to run `rake doc` in the root directory to generate them. After running the rake task the documentation will be available in the `doc` directory.
43
+ The code itself is documented using [Yard](http://yardoc.org/). The docs are not generated automatically, you need to run `rake doc` in the root directory to generate them. After running the rake task the documentation will be available in the `doc` directory. Online documentation is also available from [rubydoc.info](http://rubydoc.info/), both for [the latest gem](http://www.rubydoc.info/gems/racket-mvc) and [master](http://www.rubydoc.info/github/lasso/racket).
44
44
 
45
45
  ## Why is the code licenced under the GNU Affero General Public License? I want a more liberal licence!
46
46
  Because I think it is a Good Thing™ to share code. The
@@ -30,28 +30,7 @@ module Racket
30
30
  #
31
31
  # @return [Rack::Builder]
32
32
  def self.application
33
- return @application if @application
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
42
- instance = self
43
- @application = Rack::Builder.new do
44
- instance.settings.middleware.each do |middleware|
45
- klass, opts = middleware
46
- instance.inform_dev("Loading middleware #{klass} with settings #{opts.inspect}.")
47
- use(*middleware)
48
- end
49
- run lambda { |env|
50
- static_result = instance.serve_static_file(env)
51
- return static_result if static_result && static_result.first < 400
52
- instance.router.route(env)
53
- }
54
- end
33
+ @application ||= Utils.build_application(self)
55
34
  end
56
35
 
57
36
  # Called whenever Rack sends a request to the application.
@@ -81,10 +60,9 @@ module Racket
81
60
 
82
61
  # Initializes a new Racket::Application object with default settings.
83
62
  #
84
- # @param [true|false] reboot
85
63
  # @return [Class]
86
- def self.default(reboot = false)
87
- init({}, reboot)
64
+ def self.default
65
+ init
88
66
  end
89
67
 
90
68
  # Writes a message to the logger if there is one present.
@@ -93,7 +71,8 @@ module Racket
93
71
  # @param [Symbol] level
94
72
  # @return nil
95
73
  def self.inform(message, level)
96
- (@settings.logger.send(level, message) if @settings.logger) && nil
74
+ logger = @settings.logger
75
+ (logger.send(level, message) if logger) && nil
97
76
  end
98
77
 
99
78
  # Sends a message to the logger.
@@ -117,10 +96,8 @@ module Racket
117
96
  # Initializes the Racket application.
118
97
  #
119
98
  # @param [Hash] settings
120
- # @param [true|false] reboot
121
99
  # @return [Class]
122
- def self.init(settings, reboot)
123
- instance_variables.each { |ivar| instance_variable_set(ivar, nil) } if reboot
100
+ def self.init(settings = {})
124
101
  fail 'Application has already been initialized!' if @settings
125
102
  @settings = Settings::Application.new(settings)
126
103
  setup_static_server
@@ -134,8 +111,8 @@ module Racket
134
111
  def self.load_controllers
135
112
  inform_dev('Loading controllers.')
136
113
  @settings.store(:last_added_controller, [])
137
- Utils.files_by_longest_path(@settings.controller_dir, File.join('**', '*.rb')).each do |file|
138
- load_controller_file(file)
114
+ Utils.paths_by_longest_path(@settings.controller_dir, File.join('**', '*.rb')).each do |path|
115
+ load_controller_file(path)
139
116
  end
140
117
  @settings.delete(:last_added_controller)
141
118
  inform_dev('Done loading controllers.') && nil
@@ -146,22 +123,10 @@ module Racket
146
123
  # @param [String] file Relative path from controller dir
147
124
  # @return nil
148
125
  def self.load_controller_file(file)
149
- ::Kernel.require Utils.build_path(@settings.controller_dir, file)
150
- path = "/#{File.dirname(file)}"
151
- path = '' if path == '/.'
152
- @router.map(path, @settings.fetch(:last_added_controller).pop) && nil
153
- end
154
-
155
- # Loads some middleware (based on settings).
156
- #
157
- # @return nil
158
- def self.load_middleware
159
- middleware = @settings.middleware
160
- session_handler = @settings.session_handler
161
- default_content_type = @settings.default_content_type
162
- middleware.unshift(session_handler) if session_handler
163
- middleware.unshift([Rack::ContentType, default_content_type]) if default_content_type
164
- (middleware.unshift([Rack::ShowExceptions]) if dev_mode?) && nil
126
+ ::Kernel.require file
127
+ url_path = "/#{file.relative_path_from(@settings.controller_dir).dirname}"
128
+ url_path = '' if url_path == '/.'
129
+ @router.map(url_path, @settings.fetch(:last_added_controller).pop) && nil
165
130
  end
166
131
 
167
132
  # Reloads the application, making any changes to the controller configuration visible
@@ -181,6 +146,13 @@ module Racket
181
146
  (::Kernel.require Utils.build_path(*args)) && nil
182
147
  end
183
148
 
149
+ # Resets Racket::Application, making it possible to run run a new application with new settings.
150
+ # This is a workaround for Racket::Application being a singleton, making tests harder to write,
151
+ # @todo Remove this when Racket::Application stops beeing a singleton (if ever).
152
+ def self.reset!
153
+ instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
154
+ end
155
+
184
156
  # Serves a static file (if Racket is configured to serve static files).
185
157
  #
186
158
  # @param [Hash] env Rack environment
@@ -202,7 +174,8 @@ module Racket
202
174
  # @return [nil]
203
175
  def self.setup_static_server
204
176
  @static_server = nil
205
- return nil unless (public_dir = @settings.public_dir) && Utils.dir_readable?(public_dir)
177
+ return nil unless (public_dir = @settings.public_dir) &&
178
+ Utils.dir_readable?(Pathname.new(public_dir))
206
179
  inform_dev("Setting up static server to serve files from #{public_dir}.")
207
180
  (@static_server = Rack::File.new(public_dir)) && nil
208
181
  end
@@ -210,10 +183,9 @@ module Racket
210
183
  # Initializes a new Racket::Application object with settings specified by +settings+.
211
184
  #
212
185
  # @param [Hash] settings
213
- # @param [true|false] reboot
214
186
  # @return [Class]
215
- def self.using(settings, reboot = false)
216
- init(settings, reboot)
187
+ def self.using(settings)
188
+ init(settings)
217
189
  end
218
190
 
219
191
  # Returns the view cache of the currently running application.
@@ -223,7 +195,7 @@ module Racket
223
195
  @view_manager ||= ViewManager.new(@settings.layout_dir, @settings.view_dir)
224
196
  end
225
197
 
226
- private_class_method :application, :build_application, :inform, :init, :load_controller_file,
227
- :load_controllers, :load_middleware, :setup_routes, :setup_static_server
198
+ private_class_method :application, :inform, :init, :load_controller_file, :load_controllers,
199
+ :setup_routes, :setup_static_server
228
200
  end
229
201
  end
@@ -19,28 +19,17 @@
19
19
  module Racket
20
20
  # Base controller class. Your controllers should inherit this class.
21
21
  class Controller
22
- def self.__load_helpers(helpers)
23
- helper_dir = Application.settings.helper_dir
24
- helper_modules = {}
25
- helpers.each do |helper|
26
- helper_module = helper.to_s.split('_').collect(&:capitalize).join.to_sym
27
- Utils.run_block(NameError) do
28
- Utils.run_block(LoadError) { require "racket/helpers/#{helper}" } ||
29
- (helper_dir &&
30
- Utils.run_block(LoadError) { require Utils.build_path(helper_dir, helper) }
31
- )
32
- helper_modules[helper] = Racket::Helpers.const_get(helper_module)
33
- Application.inform_dev("Added helper module #{helper.inspect} to class #{self}.")
34
- end ||
35
- Application.inform_dev(
36
- "Failed to add helper module #{helper.inspect} to class #{self}.", :warn
37
- )
38
- end
39
- helper_modules
22
+ def self.__helper_cache
23
+ settings = Controller.settings
24
+ helper_cache =
25
+ settings.fetch(
26
+ :helper_cache,
27
+ Utils::Helpers::HelperCache.new(Application.settings.helper_dir)
28
+ )
29
+ settings.store(:helper_cache, helper_cache) unless settings.present?(:helper_cache)
30
+ helper_cache
40
31
  end
41
32
 
42
- private_class_method :__load_helpers
43
-
44
33
  # Adds a hook to one or more actions.
45
34
  #
46
35
  # @param [Symbol] type
@@ -57,8 +46,6 @@ module Racket
57
46
  nil
58
47
  end
59
48
 
60
- private_class_method :__register_hook
61
-
62
49
  # Adds a before hook to one or more actions. Actions should be given as a list of symbols.
63
50
  # If no symbols are provided, *all* actions on the controller is affected.
64
51
  #
@@ -90,12 +77,14 @@ module Racket
90
77
  helper_modules = {}
91
78
  unless settings.fetch(:helpers)
92
79
  # No helpers has been loaded yet. Load the default helpers first.
93
- helper_modules.merge!(__load_helpers(Application.settings.default_controller_helpers))
80
+ helper_modules.merge!(
81
+ __helper_cache.load_helpers(Application.settings.default_controller_helpers)
82
+ )
94
83
  end
95
84
  # Load new helpers
96
85
  helpers.map!(&:to_sym)
97
86
  helpers.reject! { |helper| helper_modules.key?(helper) }
98
- helper_modules.merge!(__load_helpers(helpers))
87
+ helper_modules.merge!(__helper_cache.load_helpers(helpers))
99
88
  setting(:helpers, helper_modules)
100
89
  end
101
90
 
@@ -120,6 +109,8 @@ module Racket
120
109
  settings.store(key, value)
121
110
  end
122
111
 
112
+ private_class_method :__helper_cache, :__register_hook
113
+
123
114
  # Returns the settings for a controller instance.
124
115
  #
125
116
  # @return [Racket::Settings::Controller]
@@ -34,10 +34,16 @@ 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 unless klass.settings.fetch(:helpers) # Makes sure default helpers are loaded.
37
+ settings = klass.settings
38
+ klass.helper unless settings.fetch(:helpers) # Makes sure default helpers are loaded.
39
+ helpers = settings.fetch(:helpers)
38
40
  properties = init_properties(action, params, env)
41
+ init_module(helpers, properties)
42
+ end
43
+
44
+ def self.init_module(helpers, properties)
39
45
  Module.new do
40
- klass.settings.fetch(:helpers).each_value { |helper| include helper }
46
+ helpers.each_value { |helper| include helper }
41
47
  properties.each_pair { |key, value| define_method(key) { value } }
42
48
  end
43
49
  end
@@ -54,6 +60,6 @@ module Racket
54
60
  properties
55
61
  end
56
62
 
57
- private_class_method :init_properties
63
+ private_class_method :init_module, :init_properties
58
64
  end
59
65
  end
@@ -27,6 +27,7 @@ module Racket
27
27
  # @param [Object] context
28
28
  # @return [String|nil]
29
29
  def render_template(template, context = self)
30
+ template = Utils.build_path(template)
30
31
  return nil unless Utils.file_readable?(template)
31
32
  Tilt.new(template).render(context)
32
33
  end
@@ -23,28 +23,24 @@ require 'http_router'
23
23
  module Racket
24
24
  # Handles routing in Racket applications.
25
25
  class Router
26
+ # A struct describing a route.
27
+ Route = Struct.new(:root, :action, :params) do
28
+ def to_s
29
+ route = root.dup
30
+ route << "/#{action}" if action
31
+ route << "/#{params.join('/')}" unless params.empty?
32
+ route = route[1..-1] if route.start_with?('//') # Special case for root path
33
+ route
34
+ end
35
+ end
36
+
26
37
  attr_reader :action_cache
27
38
  attr_reader :routes
28
39
 
29
40
  def initialize
30
41
  @router = HttpRouter.new
31
42
  @routes = {}
32
- @action_cache = {}
33
- end
34
-
35
- # Caches available actions for each controller class. This also works for controller classes
36
- # that inherit from other controller classes.
37
- #
38
- # @param [Class] controller_class
39
- # @return [nil]
40
- def cache_actions(controller_class)
41
- actions = SortedSet.new
42
- current_class = controller_class
43
- while current_class < Controller
44
- actions.merge(current_class.public_instance_methods(false))
45
- current_class = current_class.superclass
46
- end
47
- (@action_cache[controller_class] = actions.to_a) && nil
43
+ @action_cache = Utils::Routing::ActionCache.new
48
44
  end
49
45
 
50
46
  # Returns a route to the specified controller/action/parameter combination.
@@ -56,12 +52,7 @@ module Racket
56
52
  def get_route(controller_class, action, params)
57
53
  fail "Cannot find controller #{controller_class}" unless @routes.key?(controller_class)
58
54
  params.flatten!
59
- route = ''
60
- route << @routes[controller_class]
61
- route << "/#{action}" if action
62
- route << "/#{params.join('/')}" unless params.empty?
63
- route = route[1..-1] if route.start_with?('//') # Special case for root path
64
- route
55
+ Route.new(@routes[controller_class], action, params).to_s
65
56
  end
66
57
 
67
58
  # Maps a controller to the specified path.
@@ -70,11 +61,9 @@ module Racket
70
61
  # @param [Class] controller_class
71
62
  # @return [nil]
72
63
  def map(path, controller_class)
73
- controller_class_base_path = path.empty? ? '/' : path
74
- Application.inform_dev("Mapping #{controller_class} to #{controller_class_base_path}.")
64
+ map_controller(path.empty? ? '/' : path, controller_class)
75
65
  @router.add("#{path}(/*params)").to(controller_class)
76
- @routes[controller_class] = controller_class_base_path
77
- cache_actions(controller_class) && nil
66
+ @action_cache.add(controller_class)
78
67
  end
79
68
 
80
69
  # @todo: Allow the user to set custom handlers for different errors
@@ -96,15 +85,7 @@ module Racket
96
85
  catch :response do # Catches early exits from Controller.respond.
97
86
  # Ensure that that a controller will respond to the request. If not, send a 404.
98
87
  return render_error(404) unless (target_info = target_info(env))
99
- target_klass, params, action = target_info
100
-
101
- # Rewrite PATH_INFO to reflect that we split out the parameters
102
- Utils.update_path_info(env, params.length)
103
-
104
- # Initialize and render target
105
- target = target_klass.new
106
- target.extend(Current.init(env, target_klass, action, params))
107
- target.__run
88
+ Utils.render_controller(env, target_info)
108
89
  end
109
90
  rescue => err
110
91
  render_error(500, err)
@@ -112,6 +93,11 @@ module Racket
112
93
 
113
94
  private
114
95
 
96
+ def map_controller(base_path, controller_class)
97
+ Application.inform_dev("Mapping #{controller_class} to #{base_path}.")
98
+ @routes[controller_class] = base_path
99
+ end
100
+
115
101
  # Returns information about the target of the request. If no valid target can be found, +nil+
116
102
  # is returned.
117
103
  #
@@ -124,7 +110,7 @@ module Racket
124
110
  # Some controller is claiming to be responsible for the route
125
111
  result = Utils.extract_target(matching_route.first)
126
112
  # Exit early if action is not available on target
127
- return nil unless @action_cache[result.first].include?(result.last)
113
+ return nil unless @action_cache.present?(result.first, result.last)
128
114
  result
129
115
  end
130
116
  end
@@ -53,6 +53,15 @@ module Racket
53
53
  @custom.fetch(key, default)
54
54
  end
55
55
 
56
+ # Returns whether +key+ is present among the settings.
57
+ #
58
+ # @param [Symbol] key
59
+ # @return [true|false]
60
+ def present?(key)
61
+ meth = key.to_sym
62
+ respond_to?(meth) || @custom.key?(key)
63
+ end
64
+
56
65
  # Sets/updates a custom setting in the application.
57
66
  #
58
67
  # @param [Symbol] key
@@ -36,7 +36,8 @@ module Racket
36
36
  def fetch(key, default = nil)
37
37
  return @custom[key] if @custom.key?(key)
38
38
  parent = @owner.superclass
39
- return ::Racket::Application.settings.fetch(key, default) if parent == ::Racket::Controller
39
+ return ::Racket::Application.settings.fetch(key, default) if
40
+ [@owner, parent].include?(::Racket::Controller)
40
41
  parent.settings.fetch(key, default)
41
42
  end
42
43
  end
@@ -16,8 +16,10 @@
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_relative 'utils/application.rb'
19
20
  require_relative 'utils/exceptions.rb'
20
21
  require_relative 'utils/file_system.rb'
22
+ require_relative 'utils/helpers.rb'
21
23
  require_relative 'utils/routing.rb'
22
24
  require_relative 'utils/views.rb'
23
25
 
@@ -34,8 +36,10 @@ module Racket
34
36
  def_single_delegators(mod, *mod.singleton_methods) && nil
35
37
  end
36
38
 
39
+ __embrace(Application)
37
40
  __embrace(Exceptions)
38
41
  __embrace(FileSystem)
42
+ __embrace(Helpers)
39
43
  __embrace(Routing)
40
44
  __embrace(Views)
41
45
  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
+ # Collects utilities needed by different objects in Racket.
21
+ module Utils
22
+ # Utility functions for filesystem.
23
+ module Application
24
+ # Class used for building a proper Rack application.
25
+ class ApplicationBuilder
26
+ def initialize(application)
27
+ @application = application
28
+ @builder = Rack::Builder.new
29
+ @settings = @application.settings
30
+ @middleware = @settings.middleware
31
+ end
32
+
33
+ # Builds a Rack application representing Racket.
34
+ #
35
+ # @return [Proc]
36
+ def build
37
+ expand_middleware_list
38
+ add_middleware
39
+ @builder.run(application_proc)
40
+ @builder
41
+ end
42
+
43
+ private
44
+
45
+ # Add middleware to the builder.
46
+ def add_middleware
47
+ @middleware.each do |ware|
48
+ klass, opts = ware
49
+ @application.inform_dev("Loading middleware #{klass} with settings #{opts.inspect}.")
50
+ @builder.use(*ware)
51
+ end
52
+ end
53
+
54
+ # Returns a lambda that represenents that application flow.
55
+ def application_proc
56
+ application = @application
57
+ lambda do |env|
58
+ static_result = application.serve_static_file(env)
59
+ return static_result if static_result && static_result.first < 400
60
+ application.router.route(env)
61
+ end
62
+ end
63
+
64
+ # Expands middleware list based on application settings.
65
+ def expand_middleware_list
66
+ session_handler = @settings.session_handler
67
+ default_content_type = @settings.default_content_type
68
+ @middleware.unshift(session_handler) if session_handler
69
+ @middleware.unshift([Rack::ContentType, default_content_type]) if default_content_type
70
+ @middleware.unshift([Rack::ShowExceptions]) if @application.dev_mode?
71
+ end
72
+ end
73
+
74
+ # Builds and returns a Rack::Builder using the provided Racket::Application
75
+ #
76
+ # @param [Racket::Application] application
77
+ # @return [Rack::Builder]
78
+ def self.build_application(application)
79
+ ApplicationBuilder.new(application).build
80
+ end
81
+ end
82
+ end
83
+ end