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
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".constantize) 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
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
10 11 12 |
# File 'app/helpers/api/open_api_helper.rb', line 10 def current_model @model_stack.last end |
#description_for(model) ⇒ Object
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
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
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
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
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
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
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
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 |