praxis 0.15.0 → 0.16.0

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: 0dbe3630ec0519b3fe1c0c4581155e1fd9858002
4
- data.tar.gz: 1a93b0745b2413203e13a69a1a7c83e41f4c7ab8
3
+ metadata.gz: 2498e7738d76689742c8f8d3486e1e194c3c0e23
4
+ data.tar.gz: 989b01e640dd2ad1dbc8d67a320add322d76ec36
5
5
  SHA512:
6
- metadata.gz: 8f0e8f6ff18f14cbfd2e0d18fbbcb7a05ef32c01914106dd29952c63b8d2dd70fbab8c254961ca48f9ae2597206f0548acd057b820adbc5656d74d98ca7073e7
7
- data.tar.gz: 77d8c97b50f6f3853002289d59f8e17585269d5dad0d3b1d8089e00694dabd06fddfa67ed0179c918587ef6b1e624a4736b47a4b2b05478ca63ef6cde2135dda
6
+ metadata.gz: a0007fdd60eb2186c06e2d8652a4daa1e09bd8dd7d7f1c84109605d946bf32070fccb177c3fdaf4129d498610a957b44ca4a3aed4f53ddb9699ff9abbd7f2a7c
7
+ data.tar.gz: 729ea4407e325d64658f690e8f6ae9e6716bb494b36b0cf97a33e2f372901e65cf4e4330e4a4648ce867f60db3e0cbf34ae7f2d906455c83ae7bc1e2563c6660
data/CHANGELOG.md CHANGED
@@ -2,17 +2,43 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 0.16.0
6
+
7
+ * Overhauled traits: they're now represented by a `Trait` class, which are created from `ApiDefinition#trait`.
8
+ * `ApiDefinition#describe` will also include details of the defined traits.
9
+ * `ResourceDefinition#describe` and `ActionDefinition#describe` will also include the names of the used traits.
10
+ * *Note*: this may break some existing trait use cases, as they are now more-defined in their behavior, rather than simply stored blocks that are `instance_eval`-ed on the target.
11
+ * Deprecated `ResourceDefinition.routing`. Use `ResourceDefinition.prefix` to define resource-level route prefixes instead.
12
+ * Significantly refactored route generation.
13
+ * The `base_path` property defined in `ApiDefinition#info` will now appear in the routing paths 'base' (instead of simply being used for documentation purposes).
14
+ *Note*: unlike other info at that level, a global (unversioned) `base_path` is *not* overriden by specific version, rather the specific version's path is appended to the global path.
15
+ * Any prefixes set on a `ResourceDefinition` or inside a `routing` block of an ActionDefinition are now additive. For example:
16
+ * Setting a "/myresource" prefix in a "MyResource" definition, and setting a "/myaction" prefix within an action of that resource definition will result in a route containing the following segments ".../myresource/myaction...".
17
+ * Prefixes can be equally set by including `Traits`, which will follow exactly the same additive rules.
18
+ * To break the additive nature of the prefixes one can use a couple of different options:
19
+ * Define the action route path with "//" to make it absolute, i.e. a path like "//people" would not include any defined prefix.
20
+ * Explicitly clear the prefix by setting the prefix to `''` or `'//'`.
21
+ * Added `base_params` to `ApiDefinition#info` as a way to share common action params
22
+ * `base_params` may be defined for a specific Api version, which will make sharing params across all Resource definitions of that version)
23
+ * or `base_params` may be defined in the Global Api section, which will make the parameters shared across all actions of all defined Api versions.
24
+ * Fixed `MediaType#describe` to include the correct string representation of its identifier.
25
+ * Allow route options to be passed to the underlying router (i.e. Mustermann at the moment)
26
+ * routes defined in the `routing` blocks can now take any extra options which will be passed down to the Mustermann routing engine. Unknown options will be ignored!
27
+ * Displaying routes (`praxis routes` or `rake praxis:routes`) will now include any options defined in a route.
28
+ * Added an example on the instances resource of the embedded spec_app to show how to use the advanced `*` pattern and the `:except` Mustermann options (along with the required `:splat` attribute).
29
+ * Spruced up the example app (generator) to use the latest `prefix` and `trait` changes
30
+
5
31
  ## 0.15.0
6
32
 
7
33
  * Fixed handling of no app or design file groups defined in application layout.
8
- * Handled and added warning message for doc generation task when no routing block is defined for an action.
34
+ * Handled and added warning message for doc generation task when no routing block is defined for an action.
9
35
  * Improved `link` method in `MediaType` attribute definition to support inheriting the type from the `:using` option if if that specifies an attribute. For example: `link :posts, using: :posts_summary` would use the type of the `:posts_summary` attribute.
10
36
  * Fixed generated `Links` accessors to properly load the returned value.
11
37
  * Added `MediaTypeIdentifier` class to parse and manipulate Content-Type headers and Praxis::MediaType identifiers.
12
38
  * Created a registry for media type handlers that parse and generate document bodies with formats other than JSON.
13
39
  * Given a structured-data response, Praxis will convert it to JSON, XML or other formats based on the handler indicated by its Content-Type.
14
40
  * Given a request, Praxis will use the handler indicated by its Content-Type header to parse the body into structured data.
15
- * Fixed bug allowing "praxis new" to work when Praxis is installed as a system (non-bundled) gem.
41
+ * Fixed bug allowing "praxis new" to work when Praxis is installed as a system (non-bundled) gem.
16
42
  * Fixed doc generation code for custom types
17
43
  * Hardened Multipart type loading
18
44
 
@@ -220,7 +220,12 @@ module.exports = function(grunt) {
220
220
  cwd: userDocsPath,
221
221
  dest: buildPath,
222
222
  src: [
223
- 'api/**'
223
+ '**/*',
224
+ '!**/*.{js,scss,sass,less,coffee}',
225
+ '!views/**',
226
+ '!**/.*',
227
+ '!output/**'
228
+
224
229
  ]
225
230
  }]
226
231
  }
data/lib/praxis.rb CHANGED
@@ -7,17 +7,6 @@ require 'active_support/concern'
7
7
 
8
8
  $:.unshift File.dirname(__FILE__)
9
9
 
10
- module Attributor
11
- class DSLCompiler
12
- def use(name)
13
- unless Praxis::ApiDefinition.instance.traits.has_key? name
14
- raise Exceptions::InvalidTrait.new("Trait #{name} not found in the system")
15
- end
16
- self.instance_eval(&Praxis::ApiDefinition.instance.traits[name])
17
- end
18
- end
19
- end
20
-
21
10
  require 'mime'
22
11
  module MIME
23
12
  class Header
@@ -48,8 +37,10 @@ module Praxis
48
37
  autoload :ResponseTemplate, 'praxis/response_template'
49
38
  autoload :Route, 'praxis/route'
50
39
  autoload :Router, 'praxis/router'
40
+ autoload :RoutingConfig, 'praxis/routing_config'
51
41
  autoload :SimpleMediaType, 'praxis/simple_media_type'
52
42
  autoload :Stage, 'praxis/stage'
43
+ autoload :Trait, 'praxis/trait'
53
44
 
54
45
  autoload :Stats, 'praxis/stats'
55
46
  autoload :Notifications, 'praxis/notifications'
@@ -121,7 +112,4 @@ module Praxis
121
112
  autoload :Response, 'praxis/request_stages/response'
122
113
  end
123
114
 
124
- module Skeletor
125
- autoload :RestfulRoutingConfig, 'praxis/skeletor/restful_routing_config'
126
- end
127
115
  end
@@ -6,6 +6,7 @@
6
6
  #
7
7
  # Plugins may be used to extend this Config DSL.
8
8
  #
9
+
9
10
  module Praxis
10
11
  class ActionDefinition
11
12
 
@@ -15,6 +16,7 @@ module Praxis
15
16
  attr_reader :primary_route
16
17
  attr_reader :named_routes
17
18
  attr_reader :responses
19
+ attr_reader :traits
18
20
 
19
21
  # opaque hash of user-defined medata, used to decorate the definition,
20
22
  # and also available in the generated JSON documents
@@ -36,6 +38,7 @@ module Praxis
36
38
  @responses = Hash.new
37
39
  @metadata = Hash.new
38
40
  @routes = []
41
+ @traits = []
39
42
 
40
43
  if (media_type = resource_definition.media_type)
41
44
  if media_type.kind_of?(Class) && media_type < Praxis::Types::MediaTypeCommon
@@ -43,19 +46,40 @@ module Praxis
43
46
  end
44
47
  end
45
48
 
46
- resource_definition.action_defaults.each do |defaults|
47
- self.instance_eval(&defaults)
49
+ version = resource_definition.version
50
+ api_info = ApiDefinition.instance.info(resource_definition.version)
51
+
52
+ version_prefix = "#{api_info.base_path}#{resource_definition.version_prefix}"
53
+ prefix = Array(resource_definition.prefix)
54
+
55
+ @routing_config = RoutingConfig.new(version: version, base: version_prefix, prefix: prefix)
56
+
57
+ resource_definition.traits.each do |trait|
58
+ self.trait(trait)
48
59
  end
49
60
 
61
+ resource_definition.action_defaults.apply!(self)
62
+
50
63
  self.instance_eval(&block) if block_given?
51
64
  end
52
65
 
66
+ def trait(trait_name)
67
+ unless ApiDefinition.instance.traits.has_key? trait_name
68
+ raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
69
+ end
70
+
71
+ trait = ApiDefinition.instance.traits.fetch(trait_name)
72
+ trait.apply!(self)
73
+ traits << trait_name
74
+ end
75
+ alias_method :use, :trait
76
+
53
77
  def update_attribute(attribute, options, block)
54
78
  attribute.options.merge!(options)
55
79
  attribute.type.attributes(options, &block)
56
80
  end
57
81
 
58
- def response( name, **args )
82
+ def response(name, **args)
59
83
  template = ApiDefinition.instance.response(name)
60
84
  @responses[name] = template.compile(self, **args)
61
85
  end
@@ -68,13 +92,6 @@ module Praxis
68
92
  return Attributor::Attribute.new(type, opts, &block)
69
93
  end
70
94
 
71
- def use(trait_name)
72
- unless ApiDefinition.instance.traits.has_key? trait_name
73
- raise Exceptions::InvalidTrait.new("Trait #{trait_name} not found in the system")
74
- end
75
- self.instance_eval(&ApiDefinition.instance.traits[trait_name])
76
- end
77
-
78
95
  def params(type=Attributor::Struct, **opts, &block)
79
96
  return @params if !block && type == Attributor::Struct
80
97
 
@@ -87,7 +104,17 @@ module Praxis
87
104
  update_attribute(@params, opts, block)
88
105
  else
89
106
  @params = create_attribute(type, **opts, &block)
107
+ if (api_info = ApiDefinition.instance.info(resource_definition.version))
108
+ if api_info.base_params
109
+ base_attrs = api_info.base_params.attributes
110
+ @params.attributes.merge!(base_attrs) do |key, oldval, newval|
111
+ oldval
112
+ end
113
+ end
114
+ end
90
115
  end
116
+
117
+ @params
91
118
  end
92
119
 
93
120
  def payload(type=Attributor::Struct, **opts, &block)
@@ -112,8 +139,8 @@ module Praxis
112
139
  else
113
140
  type = Attributor::Hash.of(key:String) unless type
114
141
  @headers = create_attribute(type,
115
- dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true,
116
- **opts, &block)
142
+ dsl_compiler: HeadersDSLCompiler, case_insensitive_load: true,
143
+ **opts, &block)
117
144
  end
118
145
  @precomputed_header_keys_for_rack = nil #clear memoized data
119
146
  end
@@ -133,11 +160,11 @@ module Praxis
133
160
 
134
161
 
135
162
  def routing(&block)
136
- routing_config = Skeletor::RestfulRoutingConfig.new(name, resource_definition, &block)
163
+ @routing_config.instance_eval &block
137
164
 
138
- @routes = routing_config.routes
139
- @primary_route = routing_config.routes.first
140
- @named_routes = routing_config.routes.each_with_object({}) do |route, hash|
165
+ @routes = @routing_config.routes
166
+ @primary_route = @routing_config.routes.first
167
+ @named_routes = @routing_config.routes.each_with_object({}) do |route, hash|
141
168
  next if route.name.nil?
142
169
  hash[route.name] = route
143
170
  end
@@ -166,6 +193,8 @@ module Praxis
166
193
  memo[response.name] = response.describe
167
194
  memo
168
195
  end
196
+ hash[:traits] = traits if traits.any?
197
+
169
198
  self.class.doc_decorations.each do |callback|
170
199
  callback.call(self, hash)
171
200
  end
@@ -10,6 +10,7 @@ module Praxis
10
10
  attr_reader :traits
11
11
  attr_reader :responses
12
12
  attr_reader :infos
13
+ attr_reader :global_info
13
14
 
14
15
  def self.define(&block)
15
16
  if block.arity == 0
@@ -22,8 +23,12 @@ module Praxis
22
23
  def initialize
23
24
  @responses = Hash.new
24
25
  @traits = Hash.new
26
+ @base_path = ''
27
+
28
+ @global_info = ApiGeneralInfo.new
29
+
25
30
  @infos = Hash.new do |hash, version|
26
- hash[version] = ApiGeneralInfo.new
31
+ hash[version] = ApiGeneralInfo.new(@global_info)
27
32
  end
28
33
  end
29
34
 
@@ -41,35 +46,49 @@ module Praxis
41
46
  if self.traits.has_key? name
42
47
  raise Exceptions::InvalidTrait.new("Overwriting a previous trait with the same name (#{name})")
43
48
  end
44
- self.traits[name] = block
49
+ self.traits[name] = Trait.new(&block)
45
50
  end
46
51
 
47
- # Setting info to the nil version, means setting it for all versions (if they don't override them)
48
- def info( version=nil, &block)
49
- i = @infos[version]
50
- i.instance_eval(&block)
51
- i
52
+ # Setting info to the nil version, means setting it for all versions (if they don't override them)
53
+ def info(version=nil, &block)
54
+ if version.nil?
55
+ if block_given?
56
+ @global_info.instance_eval(&block)
57
+ else
58
+ @global_info
59
+ end
60
+ else
61
+ i = @infos[version]
62
+ if block_given?
63
+ i.instance_eval(&block)
64
+ end
65
+ i
66
+ end
52
67
  end
53
-
68
+
54
69
  def describe
55
- global_info = @infos[nil].describe
56
70
  data = Hash.new do |hash, version|
57
71
  hash[version] = Hash.new
58
72
  end
73
+
74
+ data[:global][:info] = @global_info.describe
75
+
59
76
  # Fill in the "info" portion
60
77
  @infos.each do |version,info|
61
- next unless version
62
- info_hash = global_info.merge(info.describe)
63
- [:name, :title].each do |attr|
64
- raise "Error: API Global information for version '#{version}' does not have '#{attr}' defined. " unless info_hash.key? attr
78
+ data[version][:info] = info.describe
79
+ end
80
+
81
+
82
+ if traits.any?
83
+ data[:traits] = {}
84
+ traits.each do |name, trait|
85
+ data[:traits][name] = trait.describe
65
86
  end
66
- data[version][:info] = info_hash
67
87
  end
68
-
69
- # Maybe report the traits?...or somehow the registered response_templates ...
88
+
70
89
  data
71
90
  end
72
-
91
+
73
92
  define do |api|
74
93
  api.response_template :ok do |media_type: |
75
94
  media_type media_type
@@ -1,36 +1,79 @@
1
1
  module Praxis
2
2
  class ApiGeneralInfo
3
-
3
+
4
+ def initialize(global_info=nil)
5
+ @global_info = global_info
6
+ @data = Hash.new
7
+ end
8
+
9
+ def get(k)
10
+ return @data[k] if @data.key?(k)
11
+ return @global_info.get(k) if @global_info
12
+ nil
13
+ end
14
+
15
+ def set(k, v)
16
+ @data[k] = v
17
+ end
18
+
4
19
  def name(val=nil)
5
- return @name unless val
6
- @name = val
20
+ if val.nil?
21
+ get(:name)
22
+ else
23
+ set(:name, val)
24
+ end
7
25
  end
8
26
 
9
27
  def title(val=nil)
10
- return @title unless val
11
- @title = val
28
+ if val.nil?
29
+ get(:title)
30
+ else
31
+ set(:title, val)
32
+ end
12
33
  end
13
34
 
14
35
  def description(val=nil)
15
- return @description unless val
16
- @description = val
36
+ if val.nil?
37
+ get(:description)
38
+ else
39
+ set(:description, val)
40
+ end
17
41
  end
18
42
 
19
43
  def base_path(val=nil)
20
- return @base_path unless val
21
- @base_path = val
44
+ if val
45
+ return set(:base_path, val)
46
+ end
47
+
48
+ if @global_info
49
+ version_path = @data.fetch(:base_path,'')
50
+ global_path = @global_info.get(:base_path)
51
+ "#{global_path}#{version_path}"
52
+ else
53
+ @data.fetch(:base_path,'')
54
+ end
55
+ end
56
+
57
+ def base_params(type=Attributor::Struct, **opts, &block)
58
+ if !block && type == Attributor::Struct
59
+ get(:base_params)
60
+ else
61
+ set(:base_params, Attributor::Attribute.new(type, opts, &block) )
62
+ end
22
63
  end
23
-
64
+
24
65
  def describe
25
66
  hash = { schema_version: "1.0".freeze }
26
-
27
67
  [:name, :title, :description, :base_path].each do |attr|
28
- val = self.__send__(attr)
68
+ val = self.__send__(attr)
29
69
  hash[attr] = val unless val.nil?
30
70
  end
71
+ if base_params
72
+ hash[:base_params] = base_params.describe[:type][:attributes]
73
+ end
31
74
  hash
32
- end
33
-
75
+ end
76
+
34
77
  end
35
-
36
- end
78
+
79
+ end
@@ -5,7 +5,6 @@ module Praxis
5
5
  # 1- Run specific controller callbacks (in addition to any normal callbacks)
6
6
  # 2- Shortcut the controller callback chain if any returns a Response object
7
7
  class RequestStage < Stage
8
- extend Forwardable
9
8
 
10
9
  alias :dispatcher :application # it's technically application in the base Stage
11
10
 
@@ -34,22 +33,19 @@ module Praxis
34
33
  setup_deferred_callbacks!
35
34
  end
36
35
 
37
- # Avoid using delegators, and create the explicit functions:
38
- # def_delegators :@context, :controller, :action, :request
39
- # they allocate all kinds of things and we don't need the generality here
40
36
  def controller
41
37
  @context.controller
42
38
  end
39
+
43
40
  def action
44
41
  @context.action
45
42
  end
43
+
46
44
  def request
47
45
  @context.request
48
46
  end
49
47
 
50
-
51
48
  def run
52
-
53
49
  # stage-level callbacks (typically empty) will never shortcut
54
50
  execute_callbacks(self.before_callbacks)
55
51