Skip to content

Commit

Permalink
refactoring settings
Browse files Browse the repository at this point in the history
  • Loading branch information
Dieter Späth committed Sep 10, 2014
1 parent 4566111 commit f859682
Show file tree
Hide file tree
Showing 43 changed files with 1,348 additions and 546 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ platforms :rbx do
gem 'rubinius-developer_tools'
gem 'racc'
end

8 changes: 7 additions & 1 deletion lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'hashie'
require 'set'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/deep_dup'

This comment has been minimized.

Copy link
@alexagranov

alexagranov Sep 25, 2014

Got excited by the refactoring so I checked out the 'master' branch but unfortunately this require breaks compatibility with pre-Rails 4.0 apps as ActiveSupport had deep_dup in the hash namespace...

This comment has been minimized.

Copy link
@dspaeth-faber

dspaeth-faber Sep 25, 2014

Contributor

@dblock What is grapes policy about supported Rails versions (this is a cas where I like nodejs's package abroach)?

This comment has been minimized.

Copy link
@dblock

dblock Sep 25, 2014

Member

I definitely think we have to support Rails 3.x, it's very common, and should be running tests with that as well. I added #763 for that.

require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/array/extract_options'
Expand Down Expand Up @@ -92,11 +93,16 @@ module Versioner
end

module Util
autoload :HashStack, 'grape/util/hash_stack'
autoload :LoggingValue, 'grape/util/logging_value'
autoload :InheritableValues, 'grape/util/inheritable_values'
autoload :StackableValues, 'grape/util/stackable_values'
autoload :InheritableSetting, 'grape/util/inheritable_setting'
end

module DSL
autoload :API, 'grape/dsl/api'
autoload :Callbacks, 'grape/dsl/callbacks'
autoload :Settings, 'grape/dsl/settings'
autoload :Configuration, 'grape/dsl/configuration'
autoload :InsideRoute, 'grape/dsl/inside_route'
autoload :Helpers, 'grape/dsl/helpers'
Expand Down
73 changes: 29 additions & 44 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,13 @@ module Grape
# creating Grape APIs.Users should subclass this
# class in order to build an API.
class API
extend Grape::Middleware::Auth::DSL

include Grape::DSL::Validations
include Grape::DSL::Callbacks
include Grape::DSL::Configuration
include Grape::DSL::Helpers
include Grape::DSL::Middleware
include Grape::DSL::RequestResponse
include Grape::DSL::Routing
include Grape::DSL::API

class << self
attr_reader :instance

LOCK = Mutex.new

def reset!
@settings = Grape::Util::HashStack.new
@route_set = Rack::Mount::RouteSet.new
@endpoints = []
@routes = nil
Expand Down Expand Up @@ -47,34 +37,19 @@ def call!(env)
#
# @param name [Symbol] Purely placebo, just allows to to name the scope to make the code more readable.
def scope(name = nil, &block)
nest(block)
within_namespace do
nest(block)
end
end

def cascade(value = nil)
if value.nil?
settings.key?(:cascade) ? !!settings[:cascade] : true
inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !!namespace_inheritable(:cascade) : true
else
set(:cascade, value)
namespace_inheritable(:cascade, value)
end
end

# Set a configuration value for this namespace.
#
# @param key [Symbol] The key of the configuration variable.
# @param value [Object] The value to which to set the configuration variable.
def set(key, value)
settings[key.to_sym] = value
end

# Add to a configuration value for this
# namespace.
#
# @param key [Symbol] The key of the configuration variable.
# @param value [Object] The value to which to set the configuration variable.
def imbue(key, value)
settings.imbue(key, value)
end

protected

def prepare_routes
Expand All @@ -91,10 +66,8 @@ def prepare_routes
def nest(*blocks, &block)
blocks.reject! { |b| b.nil? }
if blocks.any?
settings.push # create a new context to eval the follow
instance_eval(&block) if block_given?
blocks.each { |b| instance_eval(&b) }
settings.pop # when finished, we pop the context
reset_validations!
else
instance_eval(&block)
Expand All @@ -106,12 +79,14 @@ def inherited(subclass)
subclass.logger = logger.clone
end

def inherit_settings(other_stack)
settings.prepend other_stack
def inherit_settings(other_settings)
top_level_setting.inherit_from other_settings.point_in_time_copy

endpoints.each do |e|
e.settings.prepend(other_stack)
e.options[:app].inherit_settings(other_stack) if e.options[:app].respond_to?(:inherit_settings, true)
e.reset_routes!
end

@routes = nil
end
end

Expand All @@ -121,6 +96,7 @@ def initialize
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
end

@route_set.freeze
end

Expand All @@ -139,8 +115,8 @@ def call(env)
# errors from reaching upstream. This is effectivelly done by unsetting
# X-Cascade. Default :cascade is true.
def cascade?
return !!self.class.settings[:cascade] if self.class.settings.key?(:cascade)
return !!self.class.settings[:version_options][:cascade] if self.class.settings[:version_options] && self.class.settings[:version_options].key?(:cascade)
return !!self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade)
return !!self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade)
true
end

Expand All @@ -154,6 +130,7 @@ def cascade?
# cannot handle.
def add_head_not_allowed_methods_and_options_methods
methods_per_path = {}

self.class.endpoints.each do |endpoint|
routes = endpoint.routes
routes.each do |route|
Expand All @@ -169,13 +146,14 @@ def add_head_not_allowed_methods_and_options_methods
without_versioning do
methods_per_path.each do |path, methods|
allowed_methods = methods.dup
unless self.class.settings[:do_not_route_head]
unless self.class.namespace_inheritable(:do_not_route_head)
allowed_methods |= ['HEAD'] if allowed_methods.include?('GET')
end

allow_header = (['OPTIONS'] | allowed_methods).join(', ')
unless self.class.settings[:do_not_route_options]
unless self.class.namespace_inheritable(:do_not_route_options)
unless allowed_methods.include?('OPTIONS')
# require 'pry-byebug'; binding.pry
self.class.options(path, {}) do
header 'Allow', allow_header
status 204
Expand All @@ -185,7 +163,7 @@ def add_head_not_allowed_methods_and_options_methods
end

not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - allowed_methods
not_allowed_methods << 'OPTIONS' if self.class.settings[:do_not_route_options]
not_allowed_methods << 'OPTIONS' if self.class.namespace_inheritable(:do_not_route_options)
self.class.route(not_allowed_methods, path) do
header 'Allow', allow_header
status 405
Expand All @@ -196,9 +174,16 @@ def add_head_not_allowed_methods_and_options_methods
end

def without_versioning(&block)
self.class.settings.push(version: nil, version_options: nil)
old_version = self.class.namespace_inheritable(:version)
old_version_options = self.class.namespace_inheritable(:version_options)

self.class.namespace_inheritable_to_nil(:version)
self.class.namespace_inheritable_to_nil(:version_options)

yield
self.class.settings.pop

self.class.namespace_inheritable(:version, old_version)
self.class.namespace_inheritable(:version_options, old_version_options)
end
end
end
19 changes: 19 additions & 0 deletions lib/grape/dsl/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'active_support/concern'

module Grape
module DSL
module API
extend ActiveSupport::Concern

include Grape::Middleware::Auth::DSL

include Grape::DSL::Validations
include Grape::DSL::Callbacks
include Grape::DSL::Configuration
include Grape::DSL::Helpers
include Grape::DSL::Middleware
include Grape::DSL::RequestResponse
include Grape::DSL::Routing
end
end
end
10 changes: 6 additions & 4 deletions lib/grape/dsl/callbacks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,23 @@ module DSL
module Callbacks
extend ActiveSupport::Concern

include Grape::DSL::Configuration

module ClassMethods
def before(&block)
imbue(:befores, [block])
namespace_stackable(:befores, block)
end

def before_validation(&block)
imbue(:before_validations, [block])
namespace_stackable(:before_validations, block)
end

def after_validation(&block)
imbue(:after_validations, [block])
namespace_stackable(:after_validations, block)
end

def after(&block)
imbue(:afters, [block])
namespace_stackable(:afters, block)
end
end
end
Expand Down
20 changes: 16 additions & 4 deletions lib/grape/dsl/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,33 @@ module Configuration

module ClassMethods
attr_writer :logger
attr_reader :settings
# attr_reader :settings

include Grape::DSL::Settings

def logger(logger = nil)
if logger
@logger = logger
global_setting(:logger, logger)
else
@logger ||= Logger.new($stdout)
global_setting(:logger, Logger.new($stdout)) unless global_setting(:logger)
global_setting(:logger)
end
end

# Add a description to the next namespace or function.
def desc(description, options = {})
@last_description = options.merge(description: description)
namespace_setting :description, options.merge(description: description)
route_setting :description, options.merge(description: description)
end
end

module_function

def stacked_hash_to_hash(settings)
return nil if settings.nil? || settings.blank?

settings.each_with_object(ActiveSupport::OrderedHash.new) { |(value), result| result.deep_merge!(value) }
end
end
end
end
15 changes: 7 additions & 8 deletions lib/grape/dsl/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Grape
module DSL
module Helpers
extend ActiveSupport::Concern
include Grape::DSL::Configuration

module ClassMethods
# Add helper methods that will be accessible from any
Expand All @@ -27,23 +28,21 @@ module ClassMethods
#
def helpers(new_mod = nil, &block)
if block_given? || new_mod
mod = settings.peek[:helpers] || Module.new
mod = new_mod || Module.new
if new_mod
inject_api_helpers_to_mod(new_mod) if new_mod.is_a?(BaseHelper)
mod.class_eval do
include new_mod
end
end
if block_given?
inject_api_helpers_to_mod(mod) do
mod.class_eval(&block)
end
end
set(:helpers, mod)

namespace_stackable(:helpers, mod)
else
mod = Module.new
settings.stack.each do |s|
mod.send :include, s[:helpers] if s[:helpers]
namespace_stackable(:helpers).each do |mod_to_include|
mod.send :include, mod_to_include
end
change!
mod
Expand Down Expand Up @@ -77,7 +76,7 @@ def api_changed(new_api)

def process_named_params
if @named_params && @named_params.any?
api.imbue(:named_params, @named_params)
api.namespace_stackable(:named_params, @named_params)
end
end
end
Expand Down
9 changes: 5 additions & 4 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Grape
module DSL
module InsideRoute
extend ActiveSupport::Concern
include Grape::DSL::Settings

# A filtering method that will return a hash
# consisting only of keys that have been declared by a
Expand All @@ -18,8 +19,8 @@ def declared(params, options = {}, declared_params = nil)
options[:include_missing] = true unless options.key?(:include_missing)
options[:include_parent_namespaces] = true unless options.key?(:include_parent_namespaces)
if declared_params.nil?
declared_params = !options[:include_parent_namespaces] ? settings[:declared_params] :
settings.gather(:declared_params)
declared_params = (!options[:include_parent_namespaces] ? route_setting(:declared_params) :
(route_setting(:saved_declared_params) || [])).flatten(1) || []
end

unless declared_params
Expand Down Expand Up @@ -61,7 +62,7 @@ def version
# @param message [String] The message to display.
# @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set.
def error!(message, status = nil, headers = nil)
self.status(status || settings[:default_error_status])
self.status(status || namespace_inheritable(:default_error_status))
throw :error, message: message, status: self.status, headers: headers
end

Expand Down Expand Up @@ -214,7 +215,7 @@ def entity_class_for_obj(object, options)
end

object_class.ancestors.each do |potential|
entity_class ||= (settings[:representations] || {})[potential]
entity_class ||= (Grape::DSL::Configuration.stacked_hash_to_hash(namespace_stackable(:representations)) || {})[potential]
end

entity_class ||= object_class.const_get(:Entity) if object_class.const_defined?(:Entity) && object_class.const_get(:Entity).respond_to?(:represent)
Expand Down
10 changes: 5 additions & 5 deletions lib/grape/dsl/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module DSL
module Middleware
extend ActiveSupport::Concern

include Grape::DSL::Configuration

module ClassMethods
# Apply a custom middleware to the API. Applies
# to the current namespace and any children, but
Expand All @@ -15,17 +17,15 @@ module ClassMethods
def use(middleware_class, *args, &block)
arr = [middleware_class, *args]
arr << block if block_given?
imbue(:middleware, [arr])

namespace_stackable(:middleware, arr)
end

# Retrieve an array of the middleware classes
# and arguments that are currently applied to the
# application.
def middleware
settings.stack.inject([]) do |a, s|
a += s[:middleware] if s[:middleware]
a
end
namespace_stackable(:middleware) || []
end
end
end
Expand Down
Loading

3 comments on commit f859682

@etehtsea
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't want to be grumpy, but this is hard to upgrade to new version without any migration guide or documentation.

There is no more settings/set methods, but route_setting (is it right method?) has different behaviour.

@dblock
Copy link
Member

@dblock dblock commented on f859682 Jan 15, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's actually quite a bit of documentation in https://github.com/intridea/grape/blob/master/UPGRADING.md, but generally none of this stuff was an API, it was really Grape's internals that people started hacking on. I'd like to turn that into a documented API, and this refactoring was definitely a step towards that. A good example was getting rid of @last_description, hardly an API ;)

@etehtsea
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed it, sorry.

Please sign in to comment.