From 7e9d3144e713f0af3710e0af7c653f70b52213c1 Mon Sep 17 00:00:00 2001 From: Lee Richmond Date: Thu, 6 Jul 2017 11:07:54 -0400 Subject: [PATCH] Enhance generators with comments --- lib/generators/jsonapi/resource_generator.rb | 197 ++++++++++++------ .../templates/application_resource.rb.erb | 13 +- .../jsonapi/templates/controller.rb.erb | 34 +++ .../templates/destroy_request_spec.rb.erb | 2 +- .../jsonapi/templates/payload.rb.erb | 31 +++ .../jsonapi/templates/resource.rb.erb | 47 +++++ .../jsonapi/templates/serializer.rb.erb | 17 ++ 7 files changed, 280 insertions(+), 61 deletions(-) diff --git a/lib/generators/jsonapi/resource_generator.rb b/lib/generators/jsonapi/resource_generator.rb index 9d752b4..87847f9 100644 --- a/lib/generators/jsonapi/resource_generator.rb +++ b/lib/generators/jsonapi/resource_generator.rb @@ -2,85 +2,166 @@ module Jsonapi class ResourceGenerator < ::Rails::Generators::NamedBase source_root File.expand_path('../templates', __FILE__) - class_option :'no-controller', type: :boolean, default: false - class_option :'no-serializer', type: :boolean, default: false - class_option :'no-payload', type: :boolean, default: false - class_option :'no-strong-resources', type: :boolean, default: false - class_option :'no-test', type: :boolean, default: false - - desc "This generator creates a resource file at app/resources" + class_option :'omit-comments', + type: :boolean, + default: false, + aliases: ['--omit-comments', '-c'], + desc: 'Generate without documentation comments' + class_option :'omit-controller', + type: :boolean, + default: false, + aliases: ['--omit-controller'], + desc: 'Generate without controller' + class_option :'omit-serializer', + type: :boolean, + default: false, + aliases: ['--omit-serializer', '-s'], + desc: 'Generate without serializer' + class_option :'omit-payload', + type: :boolean, + default: false, + aliases: ['--omit-payload', '-p'], + desc: 'Generate without spec payload' + class_option :'omit-strong-resource', + type: :boolean, + default: false, + aliases: ['--omit-strong-resource', '-r'], + desc: 'Generate without strong resource' + class_option :'omit-route', + type: :boolean, + default: false, + aliases: ['--omit-route'], + desc: 'Generate without specs' + class_option :'omit-tests', + type: :boolean, + default: false, + aliases: ['--omit-tests', '-t'], + desc: 'Generate without specs' + + desc "This generator creates a resource file at app/resources, as well as corresponding controller/specs/route/etc" def copy_resource_file - unless @options['no-controller'] - to = File.join('app/controllers', class_path, "#{file_name.pluralize}_controller.rb") - template('controller.rb.erb', to) + unless model_klass + raise "You must define a #{class_name} model before generating the corresponding resource." end - unless @options['no-serializer'] - to = File.join('app/serializers', class_path, "serializable_#{file_name}.rb") - template('serializer.rb.erb', to) - end + generate_controller unless omit_controller? + generate_serializer unless omit_serializer? + generate_application_resource unless application_resource_defined? + generate_spec_payload unless omit_spec_payload? + generate_strong_resource unless omit_strong_resource? + generate_route unless omit_route? + generate_tests unless omit_tests? + generate_resource + end - unless 'ApplicationResource'.safe_constantize - to = File.join('app/resources', class_path, "application_resource.rb") - template('application_resource.rb.erb', to) - end + private - unless @options['no-payload'] - to = File.join('spec/payloads', class_path, "#{file_name}.rb") - template('payload.rb.erb', to) - end + def omit_comments? + @options['omit-comments'] + end + + def generate_controller + to = File.join('app/controllers', class_path, "#{file_name.pluralize}_controller.rb") + template('controller.rb.erb', to) + end - unless @options['no-strong-resources'] - inject_into_file 'config/initializers/strong_resources.rb', after: "StrongResources.configure do\n" do <<-STR + def omit_controller? + @options['omit-controller'] + end + + def generate_serializer + to = File.join('app/serializers', class_path, "serializable_#{file_name}.rb") + template('serializer.rb.erb', to) + end + + def omit_serializer? + @options['omit-serializer'] + end + + def generate_application_resource + to = File.join('app/resources', class_path, "application_resource.rb") + template('application_resource.rb.erb', to) + end + + def application_resource_defined? + 'ApplicationResource'.safe_constantize.present? + end + + def generate_spec_payload + to = File.join('spec/payloads', class_path, "#{file_name}.rb") + template('payload.rb.erb', to) + end + + def omit_spec_payload? + @options['no-payload'] + end + + def generate_strong_resource + code = <<-STR strong_resource :#{file_name} do # Your attributes go here, e.g. # attribute :name, :string end - STR - end + STR + inject_into_file 'config/initializers/strong_resources.rb', after: "StrongResources.configure do\n" do + code end + end + + def omit_strong_resource? + @options['no-strong-resources'] + end - unless @options['no-route'] - inject_into_file 'config/routes.rb', after: "scope '/api' do\n scope '/v1' do\n" do <<-STR + def generate_route + code = <<-STR resources :#{type} - STR - end + STR + inject_into_file 'config/routes.rb', after: "scope path: '/api' do\n scope path: '/v1' do\n" do + code end + end - unless @options['no-test'] - to = File.join "spec/api/v1/#{file_name.pluralize}", - class_path, - "index_spec.rb" - template('index_request_spec.rb.erb', to) - - to = File.join "spec/api/v1/#{file_name.pluralize}", - class_path, - "show_spec.rb" - template('show_request_spec.rb.erb', to) - - to = File.join "spec/api/v1/#{file_name.pluralize}", - class_path, - "create_spec.rb" - template('create_request_spec.rb.erb', to) - - to = File.join "spec/api/v1/#{file_name.pluralize}", - class_path, - "update_spec.rb" - template('update_request_spec.rb.erb', to) - - to = File.join "spec/api/v1/#{file_name.pluralize}", - class_path, - "destroy_spec.rb" - template('destroy_request_spec.rb.erb', to) - end + def omit_route? + @options['no-route'] + end + + def generate_tests + to = File.join "spec/api/v1/#{file_name.pluralize}", + class_path, + "index_spec.rb" + template('index_request_spec.rb.erb', to) + + to = File.join "spec/api/v1/#{file_name.pluralize}", + class_path, + "show_spec.rb" + template('show_request_spec.rb.erb', to) + + to = File.join "spec/api/v1/#{file_name.pluralize}", + class_path, + "create_spec.rb" + template('create_request_spec.rb.erb', to) + + to = File.join "spec/api/v1/#{file_name.pluralize}", + class_path, + "update_spec.rb" + template('update_request_spec.rb.erb', to) + + to = File.join "spec/api/v1/#{file_name.pluralize}", + class_path, + "destroy_spec.rb" + template('destroy_request_spec.rb.erb', to) + end + def omit_tests? + @options['no-test'] + end + + def generate_resource to = File.join('app/resources', class_path, "#{file_name}_resource.rb") template('resource.rb.erb', to) end - private - def model_klass class_name.safe_constantize end diff --git a/lib/generators/jsonapi/templates/application_resource.rb.erb b/lib/generators/jsonapi/templates/application_resource.rb.erb index dde80d2..6afe50d 100644 --- a/lib/generators/jsonapi/templates/application_resource.rb.erb +++ b/lib/generators/jsonapi/templates/application_resource.rb.erb @@ -1,5 +1,14 @@ -require 'jsonapi_compliable/adapters/active_record' - +<%- unless omit_comments? -%> +# ApplicationResource is similar to ApplicationRecord - a base class that +# holds configuration/methods for subclasses. +# All Resources should inherit from ApplicationResource. +# Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html +<%- end -%> class ApplicationResource < JsonapiCompliable::Resource + <%- unless omit_comments? -%> + # Use the ActiveRecord Adapter for all subclasses. + # Subclasses can still override this default. + # More on adapters: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Adapters/Abstract.html + <%- end -%> use_adapter JsonapiCompliable::Adapters::ActiveRecord end diff --git a/lib/generators/jsonapi/templates/controller.rb.erb b/lib/generators/jsonapi/templates/controller.rb.erb index c237752..b4ac69b 100644 --- a/lib/generators/jsonapi/templates/controller.rb.erb +++ b/lib/generators/jsonapi/templates/controller.rb.erb @@ -1,21 +1,46 @@ <% module_namespacing do -%> class <%= model_klass.name.pluralize %>Controller < ApplicationController + <%- unless omit_comments? -%> + # Mark this as a JSONAPI controller, associating with the given resource + <%- end -%> jsonapi resource: <%= model_klass %>Resource + <%- unless omit_comments? -%> + # Reference a strong resource payload defined in + # config/initializers/strong_resources.rb + <%- end -%> strong_resource :<%= file_name %> + <%- unless omit_comments? -%> + # Run strong parameter validation for these actions. + # Invalid keys will be dropped. + # Invalid value types will log or raise based on the configuration + # ActionController::Parameters.action_on_invalid_parameters + <%- end -%> before_action :apply_strong_params, only: [:create, :update] + <%- unless omit_comments? -%> + # Start with a base scope and pass to render_jsonapi + <%- end -%> def index <%= file_name.pluralize %> = <%= model_klass %>.all render_jsonapi(<%= file_name.pluralize %>) end + <%- unless omit_comments? -%> + # Call jsonapi_scope directly here so we can get behavior like + # sparse fieldsets and statistics. + <%- end -%> def show scope = jsonapi_scope(<%= model_klass %>.where(id: params[:id])) render_jsonapi(scope.resolve.first, scope: false) end + <%- unless omit_comments? -%> + # jsonapi_create will use the configured Resource (and adapter) to persist. + # This will handle nested relationships as well. + # On validation errors, render correct error JSON. + <%- end -%> def create <%= file_name %>, success = jsonapi_create.to_a @@ -26,6 +51,11 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController end end + <%- unless omit_comments? -%> + # jsonapi_update will use the configured Resource (and adapter) to persist. + # This will handle nested relationships as well. + # On validation errors, render correct error JSON. + <%- end -%> def update <%= file_name %>, success = jsonapi_update.to_a @@ -36,6 +66,10 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController end end + <%- unless omit_comments? -%> + # No need for any special logic here as no_content is jsonapi_compliant. + # Customize this if you have a more complex use case. + <%- end -%> def destroy <%= file_name %> = <%= model_klass %>.find(params[:id]) <%= file_name %>.destroy diff --git a/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb b/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb index 7b6c378..22a99f3 100644 --- a/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +++ b/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb @@ -2,7 +2,7 @@ require 'rails_helper' RSpec.describe "<%= type %>#destroy", type: :request do context 'basic destroy' do - let!(:<%= file_name %>) { create(:<%= file_name %>) } + let!(:<%= file_name %>) { FactoryGirl.create(:<%= file_name %>) } it 'updates the resource' do expect { diff --git a/lib/generators/jsonapi/templates/payload.rb.erb b/lib/generators/jsonapi/templates/payload.rb.erb index e38f7a6..c397c4f 100644 --- a/lib/generators/jsonapi/templates/payload.rb.erb +++ b/lib/generators/jsonapi/templates/payload.rb.erb @@ -1,2 +1,33 @@ +<%- unless omit_comments? -%> +# Register a payload to validate against. +# Add expected attributes within this block, e.g.: +# +# key(:name) +# +# Optionally validate the type as well: +# +# key(:name, String) +# +# This will: +# +# * Compare record.name == json['name'] +# * Ensure no extra keys are in the json payload +# * Ensure no values are nil (unless allow_nil: true is passed) +# * Ensures json['name'] is a string +# +# If you have custom serialization logic and want to compare against +# something other than "record.name", pass a block: +# +# key(:name) { |record| record.name.upcase } +# +# Or, if this is a one-off for a particular spec, do that customization at +# runtime: +# +# assert_payload(:person, person_record, json_item) do +# key(:name) { 'Homer Simpson' } +# end +# +# For more information, see https://jsonapi-suite.github.io/jsonapi_spec_helpers/ +<%- end -%> JsonapiSpecHelpers::Payload.register(:<%= file_name %>) do end diff --git a/lib/generators/jsonapi/templates/resource.rb.erb b/lib/generators/jsonapi/templates/resource.rb.erb index 6fdbeda..4019a08 100644 --- a/lib/generators/jsonapi/templates/resource.rb.erb +++ b/lib/generators/jsonapi/templates/resource.rb.erb @@ -1,6 +1,53 @@ +<%- unless omit_comments? -%> +# Define how to query and persist a given model. +# Further Resource documentation: https://jsonapi-suite.github.io/jsonapi_compliable/JsonapiCompliable/Resource.html +<%- end -%> <% module_namespacing do -%> class <%= class_name %>Resource < ApplicationResource + <%- unless omit_comments? -%> + # Used for associating this resource with a given input. + # This should match the 'type' in the corresponding serializer. + <%- end -%> type :<%= type %> + <%- unless omit_comments? -%> + # Associate to a Model object so we know how to persist. + <%- end -%> model <%= model_klass %> + <%- unless omit_comments? -%> + # Customize your resource here. Some common examples: + # + # === Allow ?filter[name] query parameter === + # allow_filter :name + # + # === Enable total count, when requested === + # allow_stat total: [:count] + # + # === Allow sideloading/sideposting of relationships === + # belongs_to :foo, + # foreign_key: :foo_id, + # resource: FooResource, + # scope: -> { Foo.all } + # + # === Custom sorting logic === + # sort do |scope, att, dir| + # ... code ... + # end + # + # === Change default sort === + # default_sort([{ title: :asc }]) + # + # === Custom pagination logic === + # paginate do |scope, current_page, per_page| + # ... code ... + # end + # + # === Change default page size === + # default_page_size(10) + # + # === Change how we resolve the scope === + # def resolve(scope) + # ... code ... + # end + <%- end -%> end <% end -%> diff --git a/lib/generators/jsonapi/templates/serializer.rb.erb b/lib/generators/jsonapi/templates/serializer.rb.erb index ca4c442..a2906d7 100644 --- a/lib/generators/jsonapi/templates/serializer.rb.erb +++ b/lib/generators/jsonapi/templates/serializer.rb.erb @@ -1,5 +1,22 @@ +<%- unless omit_comments? -%> +# Serializers define the rendered JSON for a model instance. +# We use jsonapi-rb, which is similar to active_model_serializers. +<%- end -%> <% module_namespacing do -%> class Serializable<%= class_name %> < JSONAPI::Serializable::Resource type :<%= type %> + + <%- unless omit_comments? -%> + # Add attributes here to ensure they get rendered, .e.g. + # + # attribute :name + # + # To customize, pass a block and reference the underlying @object + # being serialized: + # + # attribute :name do + # @object.name.upcase + # end + <%- end -%> end <% end -%>