racket-mvc 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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