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