rails_api_documentation 0.2.3 → 0.3.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -8
  3. data/app/assets/javascripts/table.js.coffee +10 -1
  4. data/app/assets/stylesheets/rails_api_doc/default.sass +39 -16
  5. data/app/assets/stylesheets/rails_api_doc/table.sass +21 -4
  6. data/app/controllers/rails_api_doc/api_docs_controller.rb +17 -20
  7. data/app/helpers/rails_api_doc/application_helper.rb +11 -0
  8. data/app/models/rails_api_doc/api_datum.rb +17 -0
  9. data/app/views/layouts/rails_api_doc/application.slim +2 -5
  10. data/app/views/rails_api_doc/api_docs/_request_api_table.slim +1 -1
  11. data/app/views/rails_api_doc/api_docs/_response_api_table.slim +4 -3
  12. data/app/views/rails_api_doc/api_docs/{index.slim → show.slim} +0 -0
  13. data/app/views/shared/_param_inputs.slim +2 -3
  14. data/app/views/shared/_table.slim +8 -6
  15. data/app/views/shared/_title.slim +8 -0
  16. data/config/routes.rb +1 -1
  17. data/lib/generators/rails_api_doc/templates/api_datum_migration.rb +4 -1
  18. data/lib/rails_api_doc.rb +16 -1
  19. data/lib/rails_api_doc/controller/headers.rb +59 -0
  20. data/lib/rails_api_doc/controller/param.rb +113 -0
  21. data/lib/rails_api_doc/controller/repo.rb +35 -0
  22. data/lib/rails_api_doc/controller/request/dsl.rb +1 -3
  23. data/lib/rails_api_doc/controller/request/factory.rb +29 -0
  24. data/lib/rails_api_doc/controller/request/headers.rb +11 -42
  25. data/lib/rails_api_doc/controller/request/param.rb +26 -33
  26. data/lib/rails_api_doc/controller/request/repository.rb +5 -26
  27. data/lib/rails_api_doc/controller/response/factory.rb +20 -2
  28. data/lib/rails_api_doc/controller/response/headers.rb +10 -21
  29. data/lib/rails_api_doc/controller/response/param.rb +38 -1
  30. data/lib/rails_api_doc/controller/response/rabl.rb +8 -19
  31. data/lib/rails_api_doc/controller/response/rabl_compiler.rb +7 -9
  32. data/lib/rails_api_doc/controller/response/repository.rb +38 -0
  33. data/lib/rails_api_doc/model/attribute_merger.rb +130 -0
  34. data/lib/rails_api_doc/model/attribute_parser.rb +23 -5
  35. data/lib/rails_api_doc/version.rb +1 -1
  36. metadata +10 -3
@@ -6,12 +6,30 @@ class RailsApiDoc::Controller::Response::Factory
6
6
  class << self
7
7
 
8
8
  # TODO: add more options later depending on app settings
9
+ # TODO: rename to :load_repo
9
10
  def repo
10
- ::RailsApiDoc::Controller::Response::Rabl.new(controllers)
11
+ attributes = RailsApiDoc::Controller::Response::Repository.new(repository.repo)
12
+
13
+ attributes = merge_attributes_from_model attributes
14
+
15
+ attributes
11
16
  end
12
17
 
13
18
  def controllers
14
- RailsApiDoc::Controller::Request::Repository.registered_controllers
19
+ RailsApiDoc::Controller::Request::Factory.registered_controllers
20
+ end
21
+
22
+ private
23
+
24
+ def repository
25
+ @repo ||= ::RailsApiDoc::Controller::Response::Rabl.new(controllers)
26
+ end
27
+
28
+ #
29
+ # do not mutate attributes
30
+ #
31
+ def merge_attributes_from_model(attributes)
32
+ RailsApiDoc::Model::AttributeMerger.new(attributes).call(api_type: 'response')
15
33
  end
16
34
 
17
35
  end
@@ -1,27 +1,16 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
1
3
  # :nodoc:
2
4
  module RailsApiDoc::Controller::Response::Headers
3
5
 
4
- RESPONSE_HEADERS = {
5
- 'Parameter' => {
6
- value: -> (row_name, row_values) { row_name },
7
- fill_type: :input,
8
- param: :name
9
- },
10
- 'Value' => {
11
- value: -> (row_name, row_values) {
12
- title = row_values.attr.to_s
13
- title += '(NESTED)' if row_values.nested?
14
- title
15
- }
16
- },
17
- 'Desc' => {
18
- value: -> (row_name, row_values) {
19
- 'TODO: description' # if row_values[:desc]
20
- },
21
- fill_type: :input,
22
- param: :desc
23
- }
24
- }
6
+ include RailsApiDoc::Controller::Headers
7
+
8
+ RESPONSE_HEADERS = [
9
+ NAME_HEADER,
10
+ TYPE_HEADER,
11
+ VALUE_HEADER,
12
+ DESC_HEADER
13
+ ].reduce(&:merge).freeze
25
14
 
26
15
  def headers
27
16
  RESPONSE_HEADERS
@@ -1,13 +1,50 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
1
3
  # :nodoc:
2
4
  module RailsApiDoc
3
5
  module Controller
4
6
  module Response
5
- class Param < Struct.new(:name, :attr, :nested, :model, :desc)
7
+
8
+ #
9
+ # name - attribute key returned in json response
10
+ # attr - actual attribute usage attribute
11
+ # nested - attribute nesting if present
12
+ # model - attribute model if nested and has model
13
+ # desc - description for attribute
14
+ # type - attribute type if set
15
+ # id - param id in DB for lookup on display
16
+ #
17
+ class Param < RailsApiDoc::Controller::Param
18
+
19
+ define_accessors *VALID_RESPONSE_KEYS
20
+
21
+ def initialize(name, attr, nested, model = nil, desc = nil, type = nil, id = nil, action_type = nil, is_new: false)
22
+ @name = name
23
+ @store = {
24
+ name: name,
25
+ attr: attr,
26
+ nested: nested,
27
+ model: model,
28
+ desc: desc,
29
+ type: type,
30
+ action_type: action_type,
31
+ id: id
32
+ }.compact
33
+
34
+ @is_new = is_new
35
+ @new = []
36
+ end
6
37
 
7
38
  def nested?
8
39
  !nested.nil?
9
40
  end
10
41
 
42
+ def display_value
43
+ title = attr.to_s
44
+ title += '(NESTED)' if nested?
45
+ title
46
+ end
47
+
11
48
  end
12
49
  end
13
50
  end
@@ -7,48 +7,37 @@ module RailsApiDoc::Controller::Response
7
7
  # :nodoc:
8
8
  class Rabl
9
9
 
10
- include RailsApiDoc::Controller::Response::Headers
11
-
12
10
  class << self
13
11
 
14
12
  attr_accessor :renderer
15
13
 
16
14
  end
17
15
 
18
- attr_reader :map
16
+ attr_reader :repo
19
17
 
20
18
  # pass all controllers registered for api doc
21
19
  # TODO: add setting for displaying all from start
22
20
  def initialize(controllers)
23
21
  @controllers = controllers
24
- @routes = Rails.application.routes.set.anchored_routes.reject { |r| r.defaults[:internal] }
25
- @map = construct_controller_template_map
26
- end
27
22
 
28
- def load_template(ctrl, action)
29
- RablCompiler.new("#{ctrl.controller_path}/#{action}").compile_source
30
- end
23
+ @routes = Rails.application.routes.set.anchored_routes.reject { |r| r.defaults[:internal] }
31
24
 
32
- def action_route(ctrl, action)
33
- action_route = @map[ctrl][:routes].detect { |r| r.defaults[:action] == action }
34
- return unless action_route
35
- method = action_route.instance_variable_get(:@request_method_match)&.first&.name&.split('::')&.last
36
- route = action_route.path.spec.to_s
37
- [method, route].compact.join(' ')
25
+ @repo = construct_controller_template_map
38
26
  end
39
27
 
40
- private
41
-
42
28
  def construct_controller_template_map
43
- @controllers.each_with_object({}) do |ctrl, map|
44
- map[ctrl] = ctrl_actions(ctrl)
29
+ @controllers.each_with_object({}) do |ctrl, repo|
30
+ repo[ctrl] = ctrl_actions(ctrl)
45
31
  end
46
32
  end
47
33
 
34
+ private
35
+
48
36
  def ctrl_actions(ctrl)
49
37
  routes = @routes.select do |route|
50
38
  route.defaults[:controller].in?([ctrl.controller_path, ctrl.controller_name])
51
39
  end
40
+
52
41
  actions = routes.map { |r| r.defaults[:action] }
53
42
  {
54
43
  routes: routes,
@@ -23,9 +23,7 @@ module RailsApiDoc::Controller::Response
23
23
  # end
24
24
 
25
25
  def add(n)
26
- if n[:attr] && n[:name] != n[:attr]
27
- n[:name] = "#{n[:name]}(#{n[:attr]})"
28
- end
26
+ n[:name] = "#{n[:name]}(#{n[:attr]})" if n[:attr] && n[:name] != n[:attr]
29
27
 
30
28
  @nodes[n[:name]] = RailsApiDoc::Controller::Response::Param.new(n[:name], n[:attr], n[:nested])
31
29
  end
@@ -159,8 +157,8 @@ module RailsApiDoc::Controller::Response
159
157
  # merge { |item| partial("specific/#{item.to_s}", object: item) }
160
158
  #
161
159
  # def merge
162
- # return unless block_given?
163
- # yield
160
+ # return unless block_given?
161
+ # yield
164
162
  # end
165
163
 
166
164
  #
@@ -181,13 +179,13 @@ module RailsApiDoc::Controller::Response
181
179
  # end
182
180
  #
183
181
  # def condition(*)
184
- # return unless block_given?
185
- # template = sub_compile(nil, true) { yield }
186
- # @attributes.extends template
182
+ # return unless block_given?
183
+ # template = sub_compile(nil, true) { yield }
184
+ # @attributes.extends template
187
185
  # end
188
186
  #
189
187
  def method_missing(name, *attrs)
190
- return p("#{name} is not implemented in railsApiDoc") if name.in?(['merge', 'condtiion'])
188
+ return p("#{name} is not implemented in railsApiDoc") if name.in?(%w(merge condtiion))
191
189
  super
192
190
  end
193
191
 
@@ -0,0 +1,38 @@
1
+ # :nodoc:
2
+ class RailsApiDoc::Controller::Response::Repository
3
+
4
+ include RailsApiDoc::Controller::Repo
5
+ include RailsApiDoc::Controller::Response::Headers
6
+
7
+ def initialize(repo)
8
+ @repo = repo.clone.transform_values { |v| v.deep_dup }
9
+ end
10
+
11
+ def load_template(ctrl, action)
12
+ RailsApiDoc::Controller::Response::RablCompiler.new("#{ctrl.controller_path}/#{action}").compile_source
13
+ end
14
+
15
+ def action_route(ctrl, action)
16
+ return unless action_route = action_route_for(ctrl, action)
17
+
18
+ {
19
+ method: method(action_route),
20
+ url: url(action_route)
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ def method(route)
27
+ route.instance_variable_get(:@request_method_match)&.first&.name&.split('::')&.last
28
+ end
29
+
30
+ def url(a_route)
31
+ a_route.path.spec.to_s.gsub(/(\(.*\))/, '')
32
+ end
33
+
34
+ def action_route_for(ctrl, action)
35
+ @repo[ctrl][:routes].detect { |r| r.defaults[:action] == action }
36
+ end
37
+
38
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ class RailsApiDoc::Model::AttributeMerger
5
+
6
+ MODEL = RailsApiDoc::ApiDatum
7
+ MERGABLE_FIELDS = [:type, :desc, :action_type].freeze
8
+
9
+ #
10
+ # do not mutate attributes
11
+ #
12
+ # please - impossible
13
+ #
14
+ def initialize(attributes)
15
+ @attrs = attributes
16
+ end
17
+
18
+ def call(api_type:)
19
+ @api_type = api_type
20
+
21
+ api_params = MODEL.where(api_type: api_type).all.each do |param|
22
+ _add_nested_param(param)
23
+ end
24
+
25
+ @attrs
26
+ end
27
+
28
+ private
29
+
30
+ def _add_nested_param(param)
31
+ # nesting should be present for parameter to appear
32
+ return if param.nesting.blank?
33
+
34
+ attrs, nesting, name = _parse_settings(param)
35
+
36
+ return unless name
37
+
38
+ ctrl_param = _find_param(nesting, name, attrs)
39
+
40
+ ctrl_param.param = param
41
+ # _merge_data_to_param(ctrl_param, param)
42
+ end
43
+
44
+ def _parse_settings(param)
45
+ ctrl = param.nesting[0].constantize
46
+ nesting = param.nesting[1..-1]
47
+ name = param.name&.to_sym
48
+ attrs = @attrs[ctrl]
49
+
50
+ #
51
+ # making exception
52
+ # until request parameters are not split by actions
53
+ #
54
+ attrs = attrs[param.api_action] if @api_type == 'response'
55
+
56
+ [attrs, nesting, name]
57
+ end
58
+
59
+ def _merge_data_to_param(ctrl_param, param)
60
+ MERGABLE_FIELDS.each do |field|
61
+ next if ctrl_param_value = ctrl_param.public_send(field) # do not override data set in api ctrl
62
+
63
+ param_value = param.public_send(field)
64
+ next if param_value.blank?
65
+
66
+ ctrl_param.public_send("#{field}=", param_value)
67
+ ctrl_param.add_updated_field(field)
68
+ end
69
+
70
+ _merge_special(ctrl_param, param)
71
+ end
72
+
73
+ def _merge_special(ctrl_param, param)
74
+ return if param.special.blank?
75
+
76
+ if ctrl_param.enum? && ctrl_param.enum.blank?
77
+ ctrl_param.enum = _parse_enum(param.special)
78
+ ctrl_param.add_updated_field('enum')
79
+ elsif ctrl_param.nested? && ctrl_param.model.blank?
80
+ ctrl_param.model = special.capitalize
81
+ ctrl_param.add_updated_field('model')
82
+ end
83
+ end
84
+
85
+ def _find_param(nesting, name, attrs)
86
+ nesting.each do |model|
87
+ nested_attrs = attrs.each_value.detect { |v| v.nested? && v.model == model }
88
+
89
+ attrs = nested_attrs ? nested_attrs : _define_nesting_level(attrs, name, model)
90
+ end
91
+
92
+ if attrs[name]
93
+ attrs[name]
94
+ else
95
+ attrs[name] = _init_param_for_api_type(name)
96
+ end
97
+ end
98
+
99
+ def _define_nesting_level(attrs, name, model)
100
+ attr = _init_param_for_api_type(name, type: :object, model: model, nested: {})
101
+
102
+ attrs[name] = attr
103
+ end
104
+
105
+ #
106
+ # common attrs:
107
+ # 1. name
108
+ # 2. type
109
+ # 3. desc
110
+ # 4. model
111
+ # 5. id
112
+ # 6. nested
113
+ #
114
+ # request attrs:
115
+ # 1. enum
116
+ # 2. value
117
+ # 3. required
118
+ #
119
+ # response attrs:
120
+ # 1. attr
121
+ #
122
+ def _init_param_for_api_type(name, options)
123
+ if @api_type == 'request'
124
+ RailsApiDoc::Controller::Request::Param.new(name, options, is_new: true)
125
+ elsif @api_type == 'response'
126
+ RailsApiDoc::Controller::Response::Param.new(name, name, options[:nested], options[:model], '', options[:type], is_new: true)
127
+ end
128
+ end
129
+
130
+ end
@@ -5,16 +5,20 @@ class RailsApiDoc::Model::AttributeParser
5
5
 
6
6
  # TODO : Change to I18n. Added on: 08.10.16. Added by: <@vshaveyko>
7
7
  WRONG_NAME_ERROR_STRING = 'Name should consist only of letters\ciphers\underscores'
8
+ WRONG_TYPE_ERROR_STRING = 'Wrong type saved'
8
9
 
9
10
  class << self
10
11
 
11
12
  def parse_attributes(params)
12
- type = :enum if params[:enum].present?
13
-
14
13
  {
15
14
  name: parse_name(params[:name]),
16
- type: type || parse_type(params[:type]),
17
- enum: parse_enum(params[:enum])
15
+ type: parse_type(params[:type]),
16
+ desc: params[:desc],
17
+ special: parse_special(params[:type], params[:special]),
18
+ action_type: params[:action],
19
+ nesting: params[:nesting],
20
+ api_type: params[:api_type],
21
+ id: params[:id]
18
22
  }.compact
19
23
  end
20
24
 
@@ -46,9 +50,23 @@ class RailsApiDoc::Model::AttributeParser
46
50
  end
47
51
  end
48
52
 
53
+ def parse_special(type, special)
54
+ return unless special.present? && type
55
+
56
+ if type == :enum
57
+ parse_enum(special) # parse as enum array value
58
+ elsif RailsApiDoc::NESTED_TYPES.include?(type.to_sym)
59
+ special.capitalize # parse as model name
60
+ end
61
+ end
62
+
49
63
  def parse_type(type)
50
64
  return if type.blank?
51
- type.constantize
65
+
66
+ type = type.to_sym
67
+ raise NameError, WRONG_TYPE_ERROR_STRING unless type.in?(RailsApiDoc::ACCEPTED_TYPES)
68
+
69
+ type
52
70
  end
53
71
 
54
72
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  # author: Vadim Shaveiko <@vshaveyko>
3
3
  module RailsApiDoc
4
- VERSION = '0.2.3'
4
+ VERSION = '0.3.0'
5
5
  end