praxis 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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