rails_api_documentation 0.1.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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +41 -0
  3. data/app/assets/config/rails_api_doc_manifest.js +2 -0
  4. data/app/assets/javascripts/api_doc.js +5 -0
  5. data/app/assets/javascripts/components.js +1 -0
  6. data/app/assets/javascripts/rails_api_doc/application.js +3 -0
  7. data/app/assets/javascripts/table.js.coffee +8 -0
  8. data/app/assets/stylesheets/rails_api_doc/application.css +15 -0
  9. data/app/assets/stylesheets/rails_api_doc/table.sass +105 -0
  10. data/app/controllers/rails_api_doc/api_docs_controller.rb +51 -0
  11. data/app/controllers/rails_api_doc/application_controller.rb +5 -0
  12. data/app/helpers/rails_api_doc/application_helper.rb +4 -0
  13. data/app/models/rails_api_doc/application_record.rb +5 -0
  14. data/app/views/layouts/rails_api_doc/application.slim +20 -0
  15. data/app/views/rails_api_doc/api_docs/_edit_field.slim +9 -0
  16. data/app/views/rails_api_doc/api_docs/_request_api_table.slim +43 -0
  17. data/app/views/rails_api_doc/api_docs/_response_api_table.slim +7 -0
  18. data/app/views/rails_api_doc/api_docs/_side_menu.slim +10 -0
  19. data/app/views/rails_api_doc/api_docs/_title.slim +4 -0
  20. data/app/views/rails_api_doc/api_docs/edit.js.erb +0 -0
  21. data/app/views/rails_api_doc/api_docs/example.html.erb +32 -0
  22. data/app/views/rails_api_doc/api_docs/index.slim +23 -0
  23. data/app/views/rails_api_doc/api_docs/new.js.erb +0 -0
  24. data/app/views/shared/_response_table.slim +14 -0
  25. data/app/views/shared/_table.slim +20 -0
  26. data/config/routes.rb +10 -0
  27. data/lib/rails_api_doc/config/validator.rb +14 -0
  28. data/lib/rails_api_doc/configuration.rb +9 -0
  29. data/lib/rails_api_doc/controller/attribute_parser.rb +56 -0
  30. data/lib/rails_api_doc/controller/parameter/repository/param.rb +51 -0
  31. data/lib/rails_api_doc/controller/parameter/repository.rb +33 -0
  32. data/lib/rails_api_doc/controller/parameter.rb +57 -0
  33. data/lib/rails_api_doc/controller/response/rabl.rb +58 -0
  34. data/lib/rails_api_doc/controller/response/rabl_compiler.rb +236 -0
  35. data/lib/rails_api_doc/controller/response_factory.rb +19 -0
  36. data/lib/rails_api_doc/controller/strong_params.rb +46 -0
  37. data/lib/rails_api_doc/controller.rb +6 -0
  38. data/lib/rails_api_doc/engine.rb +40 -0
  39. data/lib/rails_api_doc/types.rb +7 -0
  40. data/lib/rails_api_doc/version.rb +5 -0
  41. data/lib/rails_api_doc.rb +26 -0
  42. data/lib/tasks/rails_api_doc_tasks.rake +4 -0
  43. metadata +197 -0
@@ -0,0 +1,14 @@
1
+ div.flex-table
2
+ div.flex-line.row
3
+ / Угловой елемент
4
+ div.flex-item Parameter
5
+ div.flex-item Value
6
+ - locals[:rows].each do |row_name, node|
7
+ div.flex-line.row
8
+ div.flex-item #{row_name}
9
+ - if node.nested?
10
+ div.flex-item.next-is-nested #{node.attr}(Nested)
11
+ - else
12
+ div.flex-item #{node.attr}
13
+ - if node.nested?
14
+ = render 'shared/response_table', locals: { rows: node.nested }
@@ -0,0 +1,20 @@
1
+ - nesting = locals[:nesting].to_a.push(locals[:model])
2
+
3
+ div.flex-table
4
+ div.flex-line.row
5
+ / Угловой елемент
6
+ div.flex-item Parameter
7
+ div.flex-item Type
8
+ - @request_headers.each_value do |header|
9
+ div.flex-item = header
10
+ - locals[:rows].each do |row_name, row_values|
11
+ div.flex-line.row
12
+ div.flex-item #{row_name} #{'*' if row_values.required?}
13
+ - if row_values.nested?
14
+ div.flex-item.next-is-nested #{row_values[:model]}(Nested)
15
+ - else
16
+ div.flex-item #{row_values[:type]}
17
+ - @request_headers.each_key do |header_alias|
18
+ div.flex-item = row_values[header_alias]
19
+ - if row_values.nested?
20
+ = render 'shared/table', locals: { nesting: nesting, model: row_values[:model] || row_name, rows: row_values[:nested] }
data/config/routes.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # Match IDs with dots in them
4
+ # id_pattern = /[^\/]+/
5
+
6
+ RailsApiDoc::Engine.routes.draw do
7
+ resource :api_doc
8
+
9
+ root to: 'api_docs#index'
10
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ class RailsApiDoc::Config::Validator
4
+
5
+ cattr_accessor :checkers
6
+ self.checkers = []
7
+
8
+ def self.valid_param?(controller_param, api_param_data)
9
+ checkers.all? do |checker|
10
+ checker.valid?(controller_param, api_param_data)
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ class RailsApiDoc::Configuration
5
+
6
+ def initialize
7
+ end
8
+
9
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ class RailsApiDoc::Controller::AttributeParser
5
+
6
+ # TODO : Change to I18n. Added on: 08.10.16. Added by: <@vshaveyko>
7
+ WRONG_NAME_ERROR_STRING = 'Name should consist only of letters\ciphers\underscores'
8
+
9
+ class << self
10
+
11
+ def parse_attributes(params)
12
+ type = :enum if params[:enum].present?
13
+
14
+ {
15
+ name: parse_name(params[:name]),
16
+ type: type || parse_type(params[:type]),
17
+ enum: parse_enum(params[:enum])
18
+ }.compact
19
+ end
20
+
21
+ private
22
+
23
+ def parse_name(name_string)
24
+ return if name_string.blank?
25
+ raise ArgumentError, WRONG_NAME_ERROR_STRING unless name_string =~ /[A-z0-9_]*/
26
+ name_string.underscore.to_sym
27
+ end
28
+
29
+ def parse_enum(enum_string)
30
+ return if enum_string.blank?
31
+ enum_string.split(',').map do |enum_value|
32
+ parse_enum_value(enum_value)
33
+ end
34
+ end
35
+
36
+ def parse_enum_value(value)
37
+ case value
38
+ when /^\d+$/
39
+ value.to_i
40
+ when 'true'
41
+ true
42
+ when 'false'
43
+ false
44
+ else
45
+ value
46
+ end
47
+ end
48
+
49
+ def parse_type(type)
50
+ return if type.blank?
51
+ type.constantize
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ class RailsApiDoc::Controller::Parameter::Repository::Param
4
+
5
+ ACCEPTED_TYPES = [String, Integer, Object, Array, DateTime, :enum, :model].freeze
6
+
7
+ # @type - type to check
8
+ def self.accepted_nested_type?(type)
9
+ type == Object || type == :model
10
+ end
11
+
12
+ def self.valid_type?(type)
13
+ return if type.in?(ACCEPTED_TYPES)
14
+ raise ArgumentError, "Wrong type: #{type}. " \
15
+ "Correct types are: #{ACCEPTED_TYPES}."
16
+ end
17
+
18
+ def self.valid_enum?(enum)
19
+ return if enum.nil? || enum.is_a?(Array)
20
+ raise ArgumentError, 'Enum must be an array.'
21
+ end
22
+
23
+ def self.valid_nested?(type, block_given)
24
+ return false unless accepted_nested_type?(type)
25
+ return true if block_given
26
+ raise ArgumentError, 'Empty object passed.'
27
+ end
28
+
29
+ def initialize(name, store)
30
+ @name = name
31
+ @store = store
32
+ end
33
+
34
+ def nested?
35
+ self.class.accepted_nested_type?(@store[:type])
36
+ end
37
+
38
+ def required?
39
+ @store[:required]
40
+ end
41
+
42
+ def method_missing(name, *args)
43
+ return @store.send(name, *args) if respond_to_missing?(name)
44
+ super
45
+ end
46
+
47
+ def respond_to_missing?(name)
48
+ @store.respond_to?(name)
49
+ end
50
+
51
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ class RailsApiDoc::Controller::Parameter::Repository
5
+
6
+ @repo = Hash.new { |hsh, key| hsh[key] = Hash.new { |hsh, key| hsh[key] = Param.new(key) } }
7
+
8
+ class << self
9
+
10
+ def method_missing(name, *args, &block)
11
+ return @repo.send(name, *args, &block) if respond_to_missing?(name)
12
+ super
13
+ end
14
+
15
+ def registered_controllers
16
+ @repo.keys
17
+ end
18
+
19
+ def respond_to_missing?(method, *)
20
+ @repo.respond_to?(method)
21
+ end
22
+
23
+ def []=(key, value)
24
+ unless key < ActionController::Base
25
+ raise ArgumentError, 'Repository keys are controllers only'
26
+ end
27
+
28
+ super
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ module RailsApiDoc::Controller::Parameter
4
+
5
+ VALID_KEYS = [:type, :required, :enum, :model].freeze #:nodoc:
6
+
7
+ # Use parameter in controller to defined REQUEST parameter.
8
+ # Adds it to repository: RailsApiDoc::Controller::Parameter::Repository
9
+ def parameter(name, options, &block)
10
+ raise ArgumentError, 'Parameter already defined.' if repo.key?(name)
11
+
12
+ validate_options(options, block_given?)
13
+
14
+ define_parameter(name, options, &block)
15
+ end
16
+
17
+ private
18
+
19
+ def validate_options(options, block_given)
20
+ if options.nil? || options.empty?
21
+ raise ArgumentError, 'Empty options passed.'
22
+ end
23
+
24
+ options.assert_valid_keys(VALID_KEYS)
25
+
26
+ Repository::Param.valid_type?(options[:type])
27
+
28
+ Repository::Param.valid_enum?(options[:enum])
29
+
30
+ Repository::Param.valid_nested?(options[:type], block_given)
31
+ end
32
+
33
+ # default repo can be reassigned to deal with nested parameters
34
+ # see nested_parameter
35
+ def repo
36
+ @repo || Repository[self]
37
+ end
38
+
39
+ def define_parameter(name, parameter_data, &block)
40
+ repo[name] = \
41
+ if Repository::Param.valid_nested?(parameter_data[:type], block_given?)
42
+ Repository::Param.new(name, nested_parameter(parameter_data, &block))
43
+ else
44
+ Repository::Param.new(name, parameter_data)
45
+ end
46
+ end
47
+
48
+ def nested_parameter(parameter_data)
49
+ _backup_repo = @repo
50
+ @repo = {}
51
+ yield
52
+ parameter_data.merge(nested: @repo)
53
+ ensure
54
+ @repo = _backup_repo
55
+ end
56
+
57
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ # :nodoc:
5
+ class RailsApiDoc::Controller::Response
6
+
7
+ # :nodoc:
8
+ class Rabl
9
+
10
+ class << self
11
+
12
+ attr_accessor :renderer
13
+
14
+ end
15
+
16
+ attr_reader :map
17
+
18
+ # pass all controllers registered for api doc
19
+ # TODO: add setting for displaying all from start
20
+ def initialize(controllers)
21
+ @controllers = controllers
22
+ @routes = Rails.application.routes.set.anchored_routes.reject { |r| r.defaults[:internal] }
23
+ @map = construct_controller_template_map
24
+ end
25
+
26
+ def load_template(ctrl, action)
27
+ RablCompiler.new("#{ctrl.controller_path}/#{action}").compile_source
28
+ end
29
+
30
+ def action_route(ctrl, action)
31
+ action_route = @map[ctrl][:routes].detect { |r| r.defaults[:action] == action }
32
+ method = action_route.instance_variable_get(:@request_method_match).first.name.split('::').last
33
+ route = action_route.path.spec.to_s
34
+ [method, route].join(' ')
35
+ end
36
+
37
+ private
38
+
39
+ def construct_controller_template_map
40
+ @controllers.each_with_object({}) do |ctrl, map|
41
+ map[ctrl] = ctrl_actions(ctrl)
42
+ end
43
+ end
44
+
45
+ def ctrl_actions(ctrl)
46
+ routes = @routes.select do |route|
47
+ route.defaults[:controller].in?([ctrl.controller_path, ctrl.controller_name])
48
+ end
49
+ actions = routes.map { |r| r.defaults[:action] }
50
+ {
51
+ routes: routes,
52
+ actions: ctrl.action_methods
53
+ }
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+ # author: Vadim Shaveiko <@vshaveyko>
3
+ # :nodoc:
4
+ # :nodoc:
5
+ class RailsApiDoc::Controller::Response
6
+
7
+ class Node < Struct.new(:name, :attr, :nested)
8
+
9
+ def nested?
10
+ !nested.nil?
11
+ end
12
+
13
+ end
14
+
15
+ # template struct
16
+ class CompiledAttributes
17
+
18
+ attr_accessor :nodes, :data, :root_name
19
+
20
+ def initialize
21
+ @nodes = {}
22
+ # @cache_key = false
23
+ end
24
+
25
+ def each(&block)
26
+ @nodes.each &block
27
+ end
28
+
29
+ # def initialize_dup(other)
30
+ # super
31
+ # self.nodes = other.nodes.dup
32
+ # end
33
+
34
+ def add(n)
35
+ if n[:attr] && n[:name] != n[:attr]
36
+ n[:name] = "#{n[:name]}(#{n[:attr]})"
37
+ end
38
+
39
+ @nodes[n[:name]] = Node.new(n[:name], n[:attr], n[:nested])
40
+ end
41
+
42
+ def extends(template)
43
+ @nodes.merge! template.nodes
44
+ end
45
+
46
+ end
47
+ # Class that will compile RABL source code into a hash
48
+ # representing data structure
49
+ class RablCompiler
50
+
51
+ def initialize(file_path, view_path: 'app/views')
52
+ paths = Dir["#{view_path}/#{file_path}{.json,}.rabl"]
53
+ file_path = paths.find { |path| File.exist?(path) }
54
+
55
+ @source = _preserve_ivars File.read(file_path)
56
+ rescue
57
+ end
58
+
59
+ # find all instance variables used and prepend them with ':' to
60
+ # turn into symbols on compile. otherwise they turn to nil(random ivar is nil)
61
+ def _preserve_ivars(source)
62
+ source.gsub /(@[^\s]*)/, ':\1'
63
+ end
64
+
65
+ # Compile from source code and return the CompiledAttributes created.
66
+ def compile_source
67
+ return unless @source
68
+ @attributes = CompiledAttributes.new
69
+ instance_eval(@source)
70
+ @attributes
71
+ end
72
+
73
+ #
74
+ # Sets the object to be used as the data for the template
75
+ # Example:
76
+ # object :@user
77
+ # object :@user, :root => :author
78
+ #
79
+ # dont care about object / collection options. need only attributes
80
+ def object(a)
81
+ @attributes.data = a
82
+ end
83
+ alias collection object
84
+
85
+ #
86
+ # Includes the attribute or method in the output
87
+ # Example:
88
+ # attributes :id, :name
89
+ # attribute :email => :super_secret
90
+ #
91
+ # save attribute to nodes
92
+ def attribute(*args)
93
+ if args.first.is_a?(Hash)
94
+ args.first.each_pair do |key, name|
95
+ @attributes.add(name: name, attr: key)
96
+ end
97
+ else
98
+ options = args.extract_options!
99
+ args.each do |name|
100
+ key = options[:as] || name
101
+ @attributes.add(name: name, attr: key)
102
+ end
103
+ end
104
+ end
105
+ alias attributes attribute
106
+
107
+ #
108
+ # Creates a child node to be included in the output.
109
+ # name_or data can be an object or collection or a method to call on the data. It
110
+ # accepts :root and :partial options.
111
+ # Notes that partial and blocks are not compatible
112
+ # Example:
113
+ # child(:@posts, :root => :posts) { attribute :id }
114
+ # child(:posts, :partial => 'posts/base')
115
+ # child(:@posts => :cool_posts, :root => :posts) { attribute :id }
116
+ #
117
+ def child(name_or_data, options = {})
118
+ data, name = extract_data_and_name(name_or_data)
119
+
120
+ if options.empty? && new_options = name_or_data.to_a.second
121
+ options = [new_options].to_h
122
+ end
123
+
124
+ name = options[:root] if options.key? :root
125
+
126
+ if options.key?(:partial)
127
+ attrs = RablCompiler.new(options[:partial]).compile_source
128
+ elsif block_given?
129
+ attrs = sub_compile(data) { yield }
130
+ end
131
+
132
+ @attributes.add(name: name, attr: data, nested: attrs.nodes)
133
+ end
134
+
135
+ #
136
+ # Glues data from a child node to the output
137
+ # Example:
138
+ # glue(:@user) { attribute :name }
139
+ #
140
+ def glue(data)
141
+ return unless block_given?
142
+
143
+ template = sub_compile(data) { yield }
144
+
145
+ template.nodes.each_value do |value|
146
+ @attributes.add name: value.name, attr: "#{data}.#{value.attr}", nested: value.nested
147
+ end
148
+ end
149
+
150
+ #
151
+ # Creates an arbitrary node in the json output.
152
+ # It accepts :if option to create conditionnal nodes. The current data will
153
+ # be passed to the block so it is advised to use it instead of ivars.
154
+ # Example:
155
+ # node(:name) { |user| user.first_name + user.last_name }
156
+ # node(:role, if: ->(u) { !u.admin? }) { |u| u.role }
157
+ #
158
+ def node(name = nil, _options = {})
159
+ return unless block_given?
160
+ @attributes.add name: name
161
+ end
162
+ alias code node
163
+
164
+ #
165
+ # Merge arbitrary data into json output. Given block should
166
+ # return a hash.
167
+ # Example:
168
+ # merge { |item| partial("specific/#{item.to_s}", object: item) }
169
+ #
170
+ # def merge
171
+ # return unless block_given?
172
+ # yield
173
+ # end
174
+
175
+ #
176
+ # Extends an existing rabl template
177
+ # Example:
178
+ # extends 'users/base'
179
+ #
180
+ def extends(path)
181
+ extended = RablCompiler.new(path).compile_source
182
+ @attributes.extends extended
183
+ end
184
+
185
+ #
186
+ # Provide a conditionnal block
187
+ #
188
+ # condition(->(u) { u.is_a?(Admin) }) do
189
+ # attributes :secret
190
+ # end
191
+ #
192
+ # def condition(*)
193
+ # return unless block_given?
194
+ # template = sub_compile(nil, true) { yield }
195
+ # @attributes.extends template
196
+ # end
197
+ #
198
+ def method_missing(name, *attrs)
199
+ return p("#{name} is not implemented in railsApiDoc") if name.in?(['merge', 'condtiion'])
200
+ super
201
+ end
202
+
203
+ protected
204
+
205
+ #
206
+ # Extract data root_name and root name
207
+ # Example:
208
+ # :@users -> [:@users, nil]
209
+ # :@users => :authors -> [:@users, :authors]
210
+ #
211
+ def extract_data_and_name(name_or_data)
212
+ case name_or_data
213
+ when Symbol
214
+ str = name_or_data.to_s
215
+ str.start_with?('@') ? [name_or_data, str[1..-1]] : [name_or_data, name_or_data]
216
+ when Hash
217
+ name_or_data.first
218
+ else
219
+ name_or_data
220
+ end
221
+ end
222
+
223
+ def sub_compile(data, only_nodes = false)
224
+ raise unless block_given?
225
+ old_template = @attributes
226
+ @attributes = CompiledAttributes.new
227
+ yield
228
+ @attributes.data = data
229
+ only_nodes ? @attributes.nodes : @attributes
230
+ ensure
231
+ @attributes = old_template
232
+ end
233
+
234
+ end
235
+
236
+ end