Module: Api::OpenApiHelper
- Included in:
- FactoryBot::ExampleBot
- Defined in:
- app/helpers/api/open_api_helper.rb
Instance Method Summary collapse
- #automatic_components_for(model, **options) ⇒ Object
- #automatic_paths_for(model, parent, except: []) ⇒ Object
- #current_model ⇒ Object
- #description_for(model) ⇒ Object
- #external_doc(filename) ⇒ Object
- #for_model(model) ⇒ Object
- #gem_paths ⇒ Object
- #indent(string, count) ⇒ Object
- #paths_for(model) ⇒ Object
- #process_strong_parameters(model, strong_parameter_keys, schema_json, method_type, **options) ⇒ Object
- #strong_parameter_keys_for(model_name, version, method_type = "create") ⇒ Object
Instance Method Details
#automatic_components_for(model, **options) ⇒ Object
[View source]
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'app/helpers/api/open_api_helper.rb', line 50 def automatic_components_for(model, **) locals = .delete(:locals) || {} path = "app/views/api/#{@version}" paths = [path, "app/views"] + gem_paths.product(%W[/#{path} /app/views]).map(&:join) # Transform values the same way we do for Jbuilder templates Jbuilder::Schema::Template.prepend ValuesTransformer jbuilder = Jbuilder::Schema.renderer(paths, locals: { # If we ever get to the point where we need a real model here, we should implement an example team in seeds that we can source it from. model.name.underscore.split("/").last.to_sym => model.new, # Same here, if we ever need this to be a real object, this should be `test@example.com` with an `SecureRandom.hex` password. :current_user => User.new }.merge(locals)) factory_path = "test/factories/#{model.model_name.collection}.rb" cache_key = [:example, model.model_name.param_key, File.ctime(factory_path)] example = if model.name.constantize.singleton_methods.any? FactoryBot.example(model.model_name.param_key.to_sym) else Rails.cache.fetch(cache_key) { FactoryBot.example(model.model_name.param_key.to_sym) } end schema_json = jbuilder.json( example || model.new, title: I18n.t("#{model.name.underscore.pluralize}.label"), # TODO Improve this. We don't have a generic description for models we can use here. description: I18n.t("#{model.name.underscore.pluralize}.label") ) attributes_output = JSON.parse(schema_json) # Allow customization of Attributes customize_component!(attributes_output, [:attributes]) if [:attributes] # Add "Attributes" part to $ref's update_ref_values!(attributes_output) # Rails attachments aren't technically attributes in a model, # so we add the attributes manually to make them available in the API. if model..any? model..each do |reflection| attribute_name = reflection.first attributes_output["properties"][attribute_name] = { "type" => "object", "description" => attribute_name.titleize.to_s } attributes_output["example"].merge!({attribute_name.to_s => nil}) end end if has_strong_parameters?("Api::#{@version.upcase}::#{model.name.pluralize}Controller") strong_parameter_keys = strong_parameter_keys_for(model.name, @version) strong_parameter_keys_for_update = strong_parameter_keys_for(model.name, @version, "update") # Create separate parameter schema for create and update methods create_parameters_output = process_strong_parameters(model, strong_parameter_keys, schema_json, "create", **) update_parameters_output = process_strong_parameters(model, strong_parameter_keys_for_update, schema_json, "update", **) # We need to skip TeamParameters, UserParameters & InvitationParametersUpdate as they are not present in # the bullet train api schema if model.name == "Team" || model.name == "User" create_parameters_output = nil elsif model.name == "Invitation" update_parameters_output = nil end output = indent(attributes_output.to_yaml.gsub("---", "#{model.name.gsub("::", "")}Attributes:"), 3) output += indent(" " + create_parameters_output.to_yaml.gsub("---", "#{model.name.gsub("::", "")}Parameters:"), 3) if create_parameters_output output += indent(" " + update_parameters_output.to_yaml.gsub("---", "#{model.name.gsub("::", "")}ParametersUpdate:"), 3) if update_parameters_output output.html_safe else indent(attributes_output.to_yaml.gsub("---", "#{model.name.gsub("::", "")}Attributes:"), 3) .html_safe end end |
#automatic_paths_for(model, parent, except: []) ⇒ Object
[View source]
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'app/helpers/api/open_api_helper.rb', line 26 def automatic_paths_for(model, parent, except: []) output = render("api/#{@version}/open_api/shared/paths", model_name: model.model_name.collection, except: except) output = Scaffolding::Transformer.new(model.name, [parent&.name]).transform_string(output).html_safe custom_actions_file_path = "api/#{@version}/open_api/#{model.model_name.collection}/paths" custom_output = render(custom_actions_file_path).html_safe if lookup_context.exists?(custom_actions_file_path, [], true) FactoryBot::ExampleBot::REST_METHODS.each do |method| if (code = FactoryBot.send(method, model.model_name.param_key.to_sym, version: @version)) output.gsub!("🚅 #{method}", code) custom_output&.gsub!("🚅 #{method}", code) end end if custom_output merge = deep_merge(YAML.safe_load(output), YAML.safe_load(custom_output)).to_yaml.html_safe # YAML.safe_load escapes emojis https://github.com/ruby/psych/issues/371 # Next line returns emojis back and removes yaml garbage output = merge.gsub("---", "").gsub(/\\u[\da-f]{8}/i) { |m| [m[-8..].to_i(16)].pack("U") } end indent(output, 1) end |
#current_model ⇒ Object
[View source]
10 11 12 |
# File 'app/helpers/api/open_api_helper.rb', line 10 def current_model @model_stack.last end |
#description_for(model) ⇒ Object
[View source]
180 181 182 |
# File 'app/helpers/api/open_api_helper.rb', line 180 def description_for(model) external_doc "#{model.name.underscore}_description" end |
#external_doc(filename) ⇒ Object
[View source]
168 169 170 171 172 173 174 175 176 177 178 |
# File 'app/helpers/api/open_api_helper.rb', line 168 def external_doc(filename) caller_path, line_number = caller.find { |line| line.include?(".yaml.erb:") }.split(":") indentation = File.readlines(caller_path)[line_number.to_i - 1].match(/^(\s*)/)[1] path = "app/views/api/#{@version}/open_api/docs/#{filename}.md" raise "Markdown file not found: #{path}" unless File.exist?(path) File.read(path).lines.map { |line| " #{indentation}#{line}".rstrip }.join("\n").prepend("|\n").html_safe rescue Errno::ENOENT, Errno::EACCES, RuntimeError => e "Error loading markdown description: #{e.}" end |
#for_model(model) ⇒ Object
[View source]
14 15 16 17 18 19 20 |
# File 'app/helpers/api/open_api_helper.rb', line 14 def for_model(model) @model_stack ||= [] @model_stack << model result = yield @model_stack.pop result end |
#gem_paths ⇒ Object
[View source]
22 23 24 |
# File 'app/helpers/api/open_api_helper.rb', line 22 def gem_paths @gem_paths ||= `bundle show --paths`.lines.map { |gem_path| gem_path.chomp } end |
#indent(string, count) ⇒ Object
[View source]
3 4 5 6 7 8 |
# File 'app/helpers/api/open_api_helper.rb', line 3 def indent(string, count) lines = string.lines first_line = lines.shift lines = lines.map { |line| (" " * count).to_s + line } lines.unshift(first_line).join.html_safe end |
#paths_for(model) ⇒ Object
[View source]
162 163 164 165 166 |
# File 'app/helpers/api/open_api_helper.rb', line 162 def paths_for(model) for_model model do indent(render("api/#{@version}/open_api/#{model.name.underscore.pluralize}/paths"), 1) end end |
#process_strong_parameters(model, strong_parameter_keys, schema_json, method_type, **options) ⇒ Object
[View source]
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/helpers/api/open_api_helper.rb', line 131 def process_strong_parameters(model, strong_parameter_keys, schema_json, method_type, **) parameters_output = JSON.parse(schema_json) parameters_output["required"].select! { |key| strong_parameter_keys.include?(key.to_sym) } parameters_output["properties"].select! { |key| strong_parameter_keys.include?(key.to_sym) } parameters_output["example"]&.select! { |key, value| strong_parameter_keys.include?(key.to_sym) } # Allow customization of Parameters parameters_custom = [:parameters][method_type] if [:parameters].is_a?(Hash) && [:parameters].key?(method_type) parameters_custom ||= [:parameters] customize_component!(parameters_output, parameters_custom, method_type) if parameters_custom # We need to wrap the example parameters with the model name as expected by the API controllers if parameters_output["example"] parameters_output["example"] = {model.model_name.param_key => parameters_output["example"]} end parameters_output end |
#strong_parameter_keys_for(model_name, version, method_type = "create") ⇒ Object
[View source]
150 151 152 153 154 155 156 157 158 159 160 |
# File 'app/helpers/api/open_api_helper.rb', line 150 def strong_parameter_keys_for(model_name, version, method_type = "create") strong_params_module = "::Api::#{version.upcase}::#{model_name.pluralize}Controller::StrongParameters".constantize strong_params_reporter = BulletTrain::Api::StrongParametersReporter.new(model_name.constantize, strong_params_module) strong_parameter_keys = strong_params_reporter.report(method_type) if strong_parameter_keys.last.is_a?(Hash) strong_parameter_keys += strong_parameter_keys.pop.keys end strong_parameter_keys end |