lib/roda.rb in roda-1.2.0 vs lib/roda.rb in roda-1.3.0
- old
+ new
@@ -39,28 +39,40 @@
end
# Base class used for Roda requests. The instance methods for this
# class are added by Roda::RodaPlugins::Base::RequestMethods, the
# class methods are added by Roda::RodaPlugins::Base::RequestClassMethods.
- class RodaRequest < ::Rack::Request;
+ class RodaRequest < ::Rack::Request
@roda_class = ::Roda
@match_pattern_cache = ::Roda::RodaCache.new
end
# Base class used for Roda responses. The instance methods for this
# class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
# methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
- class RodaResponse < ::Rack::Response;
+ class RodaResponse
@roda_class = ::Roda
end
@app = nil
@inherit_middleware = true
@middleware = []
@opts = {}
@route_block = nil
+ module RodaDeprecateMutation
+ [:[]=, :clear, :compare_by_identity, :default=, :default_proc=, :delete, :delete_if,
+ :keep_if, :merge!, :reject!, :replace, :select!, :shift, :store, :update].each do |m|
+ class_eval(<<-END, __FILE__, __LINE__+1)
+ def #{m}(*)
+ RodaPlugins.deprecate("Mutating this hash (\#{inspect}) via the #{m} method is deprecated, this hash will be frozen in Roda 2.")
+ super
+ end
+ END
+ end
+ end
+
# Module in which all Roda plugins should be stored. Also contains logic for
# registering and loading plugins.
module RodaPlugins
# Stores registered plugins
@plugins = RodaCache.new
@@ -84,10 +96,16 @@
# Roda::RodaPlugins.register_plugin(:plugin_name, PluginModule)
def self.register_plugin(name, mod)
@plugins[name] = mod
end
+ # Emit a deprecation message. By default this just calls warn. You can override this
+ # method to log deprecation messages to a file or include backtraces (or something else).
+ def self.deprecate(msg)
+ warn(msg)
+ end
+
# The base plugin for Roda, implementing all default functionality.
# Methods are put into a plugin so future plugins can easily override
# them and call super to get the default behavior.
module Base
# Class methods for the Roda class.
@@ -118,40 +136,42 @@
def clear_middleware!
@middleware.clear
build_rack_app
end
- # Create a match_#{key} method in the request class using the given
- # block, so that using a hash key in a request match method will
- # call the block. The block should return nil or false to not
- # match, and anything else to match.
+ # Freeze the internal state of the class, to avoid thread safety issues at runtime.
+ # It's optional to call this method, as nothing should be modifying the
+ # internal state at runtime anyway, but this makes sure an exception will
+ # be raised if you try to modify the internal state after calling this.
#
- # class App < Roda
- # hash_matcher(:foo) do |v|
- # self['foo'] == v
- # end
- #
- # route do
- # r.on :foo=>'bar' do
- # # matches when param foo has value bar
- # end
- # end
- # end
+ # Note that freezing the class prevents you from subclassing it, mostly because
+ # it would cause some plugins to break.
+ def freeze
+ @opts.freeze
+ @middleware.freeze
+ super
+ end
+
def hash_matcher(key, &block)
- request_module{define_method(:"match_#{key}", &block)}
+ RodaPlugins.deprecate("Roda.hash_matcher is deprecated and will be removed in Roda 2. It has been moved to the hash_matcher plugin.")
+ self::RodaRequest.send(:define_method, :"match_#{key}", &block)
end
# When inheriting Roda, copy the shared data into the subclass,
# and setup the request and response subclasses.
def inherited(subclass)
+ raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
super
subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
subclass.instance_variable_set(:@opts, opts.dup)
subclass.opts.to_a.each do |k,v|
if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
subclass.opts[k] = v.dup
+ if v.is_a?(RodaDeprecateMutation)
+ subclass.opts[k].extend(RodaDeprecateMutation)
+ end
end
end
subclass.instance_variable_set(:@route_block, @route_block)
subclass.send(:build_rack_app)
@@ -170,77 +190,29 @@
# which will be required and then used.
#
# Roda.plugin PluginModule
# Roda.plugin :csrf
def plugin(plugin, *args, &block)
- if plugin.is_a?(Symbol)
- plugin = RodaPlugins.load_plugin(plugin)
- end
-
- if plugin.respond_to?(:load_dependencies)
- plugin.load_dependencies(self, *args, &block)
- end
-
- if defined?(plugin::InstanceMethods)
- include(plugin::InstanceMethods)
- end
- if defined?(plugin::ClassMethods)
- extend(plugin::ClassMethods)
- end
- if defined?(plugin::RequestMethods)
- self::RodaRequest.send(:include, plugin::RequestMethods)
- end
- if defined?(plugin::RequestClassMethods)
- self::RodaRequest.extend(plugin::RequestClassMethods)
- end
- if defined?(plugin::ResponseMethods)
- self::RodaResponse.send(:include, plugin::ResponseMethods)
- end
- if defined?(plugin::ResponseClassMethods)
- self::RodaResponse.extend(plugin::ResponseClassMethods)
- end
-
- if plugin.respond_to?(:configure)
- plugin.configure(self, *args, &block)
- end
+ raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
+ plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
+ plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
+ include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
+ extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
+ self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods)
+ self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods)
+ self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods)
+ self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods)
+ plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
end
- # Include the given module in the request class. If a block
- # is provided instead of a module, create a module using the
- # the block. Example:
- #
- # Roda.request_module SomeModule
- #
- # Roda.request_module do
- # def description
- # "#{request_method} #{path_info}"
- # end
- # end
- #
- # Roda.route do |r|
- # r.description
- # end
def request_module(mod = nil, &block)
+ RodaPlugins.deprecate("Roda.request_module is deprecated and will be removed in Roda 2. It has been moved to the module_include plugin.")
module_include(:request, mod, &block)
end
- # Include the given module in the response class. If a block
- # is provided instead of a module, create a module using the
- # the block. Example:
- #
- # Roda.response_module SomeModule
- #
- # Roda.response_module do
- # def error!
- # self.status = 500
- # end
- # end
- #
- # Roda.route do |r|
- # response.error!
- # end
def response_module(mod = nil, &block)
+ RodaPlugins.deprecate("Roda.response_module is deprecated and will be removed in Roda 2. It has been moved to the module_include plugin.")
module_include(:response, mod, &block)
end
# Setup routing tree for the current Roda application, and build the
# underlying rack application using the stored middleware. Requires
@@ -269,11 +241,11 @@
# Add a middleware to use for the rack application. Must be
# called before calling #route to have an effect. Example:
#
# Roda.use Rack::Session::Cookie, :secret=>ENV['secret']
def use(*args, &block)
- @middleware << [args, block]
+ @middleware << [args, block].freeze
build_rack_app
end
private
@@ -285,11 +257,11 @@
builder.run lambda{|env| allocate.call(env, &block)}
@app = builder.to_app
end
end
- # Backbone of the request_module and response_module support.
+ # REMOVE20
def module_include(type, mod)
if type == :response
klass = self::RodaResponse
iv = :@response_module
else
@@ -355,11 +327,11 @@
def response
@_response
end
# The session hash for the current request. Raises RodaError
- # if no session existsExample:
+ # if no session exists. Example:
#
# session # => {}
def session
@_request.session
end
@@ -597,12 +569,16 @@
# # => '/foo/bar'
def path
e = @env
"#{e[SCRIPT_NAME]}#{e[PATH_INFO]}"
end
- alias full_path_info path
+ def full_path_info
+ RodaPlugins.deprecate("RodaRequest#full_path_info is deprecated and will be removed in Roda 2. Switch to using #path.")
+ path
+ end
+
# The current path to match requests against. This is the same as PATH_INFO
# in the environment, which gets updated as the request is being routed.
def remaining_path
@env[PATH_INFO]
end
@@ -647,11 +623,11 @@
# is to override the response status and headers:
#
# response.status = 200
# response['Header-Name'] = 'Header value'
def response
- scope.response
+ @scope.response
end
# Return the Roda class related to this request.
def roda_class
self.class.roda_class
@@ -886,13 +862,12 @@
# Match only if all of the arguments in the given array match.
def match_all(args)
args.all?{|arg| match(arg)}
end
- # Match files with the given extension. Requires that the
- # request path end with the extension.
def match_extension(ext)
+ RodaPlugins.deprecate("The :extension matcher is deprecated and will be removed in Roda 2. It has been moved to the path_matchers plugin.")
consume(self.class.cached_matcher([:extension, ext]){/([^\\\/]+)\.#{ext}/})
end
# Match by request method. This can be an array if you want
# to match on multiple methods.
@@ -902,21 +877,19 @@
else
type.to_s.upcase == @env[REQUEST_METHOD]
end
end
- # Match the given parameter if present, even if the parameter is empty.
- # Adds any match to the captures.
def match_param(key)
+ RodaPlugins.deprecate("The :param matcher is deprecated and will be removed in Roda 2. It has been moved to the param_matchers plugin.")
if v = self[key]
@captures << v
end
end
- # Match the given parameter if present and not empty.
- # Adds any match to the captures.
def match_param!(key)
+ RodaPlugins.deprecate("The :param! matcher is deprecated and will be removed in Roda 2. It has been moved to the param_matchers plugin.")
if (v = self[key]) && !v.empty?
@captures << v
end
end
@@ -947,10 +920,13 @@
module ResponseMethods
CONTENT_LENGTH = "Content-Length".freeze
DEFAULT_HEADERS = {"Content-Type" => "text/html".freeze}.freeze
LOCATION = "Location".freeze
+ # The body for the current response.
+ attr_reader :body
+
# The hash of response headers for the current response.
attr_reader :headers
# The status code to use for the response. If none is given, will use 200
# code for non-empty responses and a 404 code for empty responses.
@@ -981,18 +957,12 @@
# The default headers to use for responses.
def default_headers
DEFAULT_HEADERS
end
- # Modify the headers to include a Set-Cookie value that
- # deletes the cookie. A value hash can be provided to
- # override the default one used to delete the cookie.
- # Example:
- #
- # response.delete_cookie('foo')
- # response.delete_cookie('foo', :domain=>'example.org')
def delete_cookie(key, value = {})
+ RodaPlugins.deprecate("RodaResponse#delete_cookie is deprecated and will be removed in Roda 2. It has been moved to the cookies plugin.")
::Rack::Utils.delete_cookie_header!(@headers, key, value)
end
# Whether the response body has been written to yet. Note
# that writing an empty string to the response body marks
@@ -1053,14 +1023,11 @@
# Return the Roda class related to this response.
def roda_class
self.class.roda_class
end
- # Set the cookie with the given key in the headers.
- #
- # response.set_cookie('foo', 'bar')
- # response.set_cookie('foo', :value=>'bar', :domain=>'example.org')
def set_cookie(key, value)
+ RodaPlugins.deprecate("RodaResponse#set_cookie is deprecated and will be removed in Roda 2. It has been moved to the cookies plugin.")
::Rack::Utils.set_cookie_header!(@headers, key, value)
end
# Write to the response body. Returns nil.
#