Skip to content

Commit 11fee4f

Browse files
author
Lee Richmond
committed
Enhance generators
* Can now specify attributes, e.g. `rails g jsonapi:resource post title:string` * Can now specify controller actions, e.g. `rails g jsonapi:resource post -a index show destroy`
1 parent 109ea6e commit 11fee4f

9 files changed

+123
-95
lines changed

lib/generators/jsonapi/resource_generator.rb

Lines changed: 97 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,49 @@ module Jsonapi
22
class ResourceGenerator < ::Rails::Generators::NamedBase
33
source_root File.expand_path('../templates', __FILE__)
44

5+
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
6+
57
class_option :'omit-comments',
68
type: :boolean,
79
default: false,
810
aliases: ['--omit-comments', '-c'],
911
desc: 'Generate without documentation comments'
10-
class_option :'omit-controller',
11-
type: :boolean,
12-
default: false,
13-
aliases: ['--omit-controller'],
14-
desc: 'Generate without controller'
15-
class_option :'omit-serializer',
16-
type: :boolean,
17-
default: false,
18-
aliases: ['--omit-serializer', '-s'],
19-
desc: 'Generate without serializer'
20-
class_option :'omit-payload',
21-
type: :boolean,
22-
default: false,
23-
aliases: ['--omit-payload', '-p'],
24-
desc: 'Generate without spec payload'
25-
class_option :'omit-strong-resource',
26-
type: :boolean,
27-
default: false,
28-
aliases: ['--omit-strong-resource', '-r'],
29-
desc: 'Generate without strong resource'
30-
class_option :'omit-route',
31-
type: :boolean,
32-
default: false,
33-
aliases: ['--omit-route'],
34-
desc: 'Generate without specs'
35-
class_option :'omit-tests',
36-
type: :boolean,
37-
default: false,
38-
aliases: ['--omit-tests', '-t'],
39-
desc: 'Generate without specs'
12+
class_option :'actions',
13+
type: :array,
14+
default: nil,
15+
aliases: ['--actions', '-a'],
16+
desc: 'Array of controller actions to support, e.g. "index show destroy"'
4017

4118
desc "This generator creates a resource file at app/resources, as well as corresponding controller/specs/route/etc"
4219
def copy_resource_file
4320
unless model_klass
4421
raise "You must define a #{class_name} model before generating the corresponding resource."
4522
end
4623

47-
generate_controller unless omit_controller?
48-
generate_serializer unless omit_serializer?
24+
generate_controller
25+
generate_serializer
4926
generate_application_resource unless application_resource_defined?
50-
generate_spec_payload unless omit_spec_payload?
51-
generate_strong_resource unless omit_strong_resource?
52-
generate_route unless omit_route?
53-
generate_tests unless omit_tests?
27+
generate_spec_payload
28+
29+
if actions?('create', 'update')
30+
generate_strong_resource
31+
end
32+
33+
generate_route
34+
generate_tests
5435
generate_resource
5536
end
5637

5738
private
5839

40+
def actions
41+
@options['actions'] || %w(index show create update destroy)
42+
end
43+
44+
def actions?(*methods)
45+
methods.any? { |m| actions.include?(m) }
46+
end
47+
5948
def omit_comments?
6049
@options['omit-comments']
6150
end
@@ -65,19 +54,11 @@ def generate_controller
6554
template('controller.rb.erb', to)
6655
end
6756

68-
def omit_controller?
69-
@options['omit-controller']
70-
end
71-
7257
def generate_serializer
7358
to = File.join('app/serializers', class_path, "serializable_#{file_name}.rb")
7459
template('serializer.rb.erb', to)
7560
end
7661

77-
def omit_serializer?
78-
@options['omit-serializer']
79-
end
80-
8162
def generate_application_resource
8263
to = File.join('app/resources', class_path, "application_resource.rb")
8364
template('application_resource.rb.erb', to)
@@ -92,76 +73,103 @@ def generate_spec_payload
9273
template('payload.rb.erb', to)
9374
end
9475

95-
def omit_spec_payload?
96-
@options['no-payload']
97-
end
98-
9976
def generate_strong_resource
100-
code = <<-STR
101-
strong_resource :#{file_name} do
102-
# Your attributes go here, e.g.
103-
# attribute :name, :string
104-
end
77+
code = " strong_resource :#{file_name} do\n"
78+
attributes.each do |a|
79+
code << " attribute :#{a.name}, :#{a.type}\n"
80+
end
81+
code << " end\n"
10582

106-
STR
10783
inject_into_file 'config/initializers/strong_resources.rb', after: "StrongResources.configure do\n" do
10884
code
10985
end
11086
end
11187

112-
def omit_strong_resource?
113-
@options['no-strong-resources']
114-
end
115-
11688
def generate_route
117-
code = <<-STR
118-
resources :#{type}
119-
STR
89+
code = " resources :#{type}"
90+
code << ", only: [#{actions.map { |a| ":#{a}" }.join(', ')}]" if actions.length < 5
91+
code << "\n"
12092
inject_into_file 'config/routes.rb', after: "scope path: '/v1' do\n" do
12193
code
12294
end
12395
end
12496

125-
def omit_route?
126-
@options['no-route']
127-
end
128-
12997
def generate_tests
130-
to = File.join "spec/api/v1/#{file_name.pluralize}",
131-
class_path,
132-
"index_spec.rb"
133-
template('index_request_spec.rb.erb', to)
134-
135-
to = File.join "spec/api/v1/#{file_name.pluralize}",
136-
class_path,
137-
"show_spec.rb"
138-
template('show_request_spec.rb.erb', to)
98+
if actions?('index')
99+
to = File.join "spec/api/v1/#{file_name.pluralize}",
100+
class_path,
101+
"index_spec.rb"
102+
template('index_request_spec.rb.erb', to)
103+
end
139104

140-
to = File.join "spec/api/v1/#{file_name.pluralize}",
141-
class_path,
142-
"create_spec.rb"
143-
template('create_request_spec.rb.erb', to)
105+
if actions?('show')
106+
to = File.join "spec/api/v1/#{file_name.pluralize}",
107+
class_path,
108+
"show_spec.rb"
109+
template('show_request_spec.rb.erb', to)
110+
end
144111

145-
to = File.join "spec/api/v1/#{file_name.pluralize}",
146-
class_path,
147-
"update_spec.rb"
148-
template('update_request_spec.rb.erb', to)
112+
if actions?('create')
113+
to = File.join "spec/api/v1/#{file_name.pluralize}",
114+
class_path,
115+
"create_spec.rb"
116+
template('create_request_spec.rb.erb', to)
117+
end
149118

150-
to = File.join "spec/api/v1/#{file_name.pluralize}",
151-
class_path,
152-
"destroy_spec.rb"
153-
template('destroy_request_spec.rb.erb', to)
154-
end
119+
if actions?('update')
120+
to = File.join "spec/api/v1/#{file_name.pluralize}",
121+
class_path,
122+
"update_spec.rb"
123+
template('update_request_spec.rb.erb', to)
124+
end
155125

156-
def omit_tests?
157-
@options['no-test']
126+
if actions?('destroy')
127+
to = File.join "spec/api/v1/#{file_name.pluralize}",
128+
class_path,
129+
"destroy_spec.rb"
130+
template('destroy_request_spec.rb.erb', to)
131+
end
158132
end
159133

160134
def generate_resource
161135
to = File.join('app/resources', class_path, "#{file_name}_resource.rb")
162136
template('resource.rb.erb', to)
163137
end
164138

139+
def jsonapi_config
140+
File.exists?('.jsonapicfg.yml') ? YAML.load_file('.jsonapicfg.yml') : {}
141+
end
142+
143+
def update_config!(attrs)
144+
config = jsonapi_config.merge(attrs)
145+
File.open('.jsonapicfg.yml', 'w') { |f| f.write(config.to_yaml) }
146+
end
147+
148+
def prompt(header: nil, description: nil, default: nil)
149+
say(set_color("\n#{header}", :magenta, :bold)) if header
150+
say("\n#{description}") if description
151+
answer = ask(set_color("\n(default: #{default}):", :magenta, :bold))
152+
answer = default if answer.blank? && default != 'nil'
153+
say(set_color("\nGot it!\n", :white, :bold))
154+
answer
155+
end
156+
157+
def api_namespace
158+
@api_namespace ||= begin
159+
ns = jsonapi_config['namespace']
160+
161+
if ns.blank?
162+
ns = prompt \
163+
header: "What is your API namespace?",
164+
description: "This will be used as a route prefix, e.g. if you want the route '/books_api/v1/authors' your namespace would be 'books_api'",
165+
default: 'api'
166+
update_config!('namespace' => ns)
167+
end
168+
169+
ns
170+
end
171+
end
172+
165173
def model_klass
166174
class_name.safe_constantize
167175
end

lib/generators/jsonapi/templates/controller.rb.erb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
55
<%- end -%>
66
jsonapi resource: <%= model_klass %>Resource
77

8+
<%- if actions?('create', 'update') -%>
89
<%- unless omit_comments? -%>
910
# Reference a strong resource payload defined in
1011
# config/initializers/strong_resources.rb
1112
<%- end -%>
1213
strong_resource :<%= file_name %>
13-
14+
<%- end -%>
15+
<%- if actions?('create', 'update') -%>
1416
<%- unless omit_comments? -%>
1517
# Run strong parameter validation for these actions.
1618
# Invalid keys will be dropped.
@@ -19,6 +21,8 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
1921
<%- end -%>
2022
before_action :apply_strong_params, only: [:create, :update]
2123

24+
<%- end -%>
25+
<%- if actions?('index') -%>
2226
<%- unless omit_comments? -%>
2327
# Start with a base scope and pass to render_jsonapi
2428
<%- end -%>
@@ -27,6 +31,8 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
2731
render_jsonapi(<%= file_name.pluralize %>)
2832
end
2933

34+
<%- end -%>
35+
<%- if actions?('show') -%>
3036
<%- unless omit_comments? -%>
3137
# Call jsonapi_scope directly here so we can get behavior like
3238
# sparse fieldsets and statistics.
@@ -36,6 +42,8 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
3642
render_jsonapi(scope.resolve.first, scope: false)
3743
end
3844

45+
<%- end -%>
46+
<%- if actions?('create') -%>
3947
<%- unless omit_comments? -%>
4048
# jsonapi_create will use the configured Resource (and adapter) to persist.
4149
# This will handle nested relationships as well.
@@ -51,6 +59,8 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
5159
end
5260
end
5361

62+
<%- end -%>
63+
<%- if actions?('update') -%>
5464
<%- unless omit_comments? -%>
5565
# jsonapi_update will use the configured Resource (and adapter) to persist.
5666
# This will handle nested relationships as well.
@@ -66,6 +76,8 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
6676
end
6777
end
6878

79+
<%- end -%>
80+
<%- if actions?('destroy') -%>
6981
<%- unless omit_comments? -%>
7082
# No need for any special logic here as no_content is jsonapi_compliant.
7183
# Customize this if you have a more complex use case.
@@ -75,5 +87,6 @@ class <%= model_klass.name.pluralize %>Controller < ApplicationController
7587
<%= file_name %>.destroy
7688
return head(:no_content)
7789
end
90+
<%- end -%>
7891
end
7992
<% end -%>

lib/generators/jsonapi/templates/create_request_spec.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ RSpec.describe "<%= type %>#create", type: :request do
1515

1616
it 'creates the resource' do
1717
expect {
18-
jsonapi_post "/api/v1/<%= type %>", payload
18+
jsonapi_post "/<%= api_namespace %>/v1/<%= type %>", payload
1919
}.to change { <%= model_klass %>.count }.by(1)
2020
<%= file_name %> = <%= model_klass %>.last
2121

lib/generators/jsonapi/templates/destroy_request_spec.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ RSpec.describe "<%= type %>#destroy", type: :request do
66

77
it 'updates the resource' do
88
expect {
9-
delete "/api/v1/<%= type %>/#{<%= file_name %>.id}"
9+
delete "/<%= api_namespace %>/v1/<%= type %>/#{<%= file_name %>.id}"
1010
}.to change { <%= model_klass %>.count }.by(-1)
1111

1212
expect(response.status).to eq(204)

lib/generators/jsonapi/templates/index_request_spec.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ RSpec.describe "<%= file_name.pluralize %>#index", type: :request do
66
let!(:<%= file_name %>2) { create(:<%= file_name %>) }
77

88
it 'serializes the list correctly' do
9-
get "/api/v1/<%= file_name.pluralize %>"
9+
get "/<%= api_namespace %>/v1/<%= file_name.pluralize %>"
1010

1111
expect(json_ids(true)).to match_array([<%= file_name %>1.id, <%= file_name %>2.id])
1212
assert_payload(:<%= file_name %>, <%= file_name %>1, json_items[0])

lib/generators/jsonapi/templates/payload.rb.erb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@
3030
# For more information, see https://jsonapi-suite.github.io/jsonapi_spec_helpers/
3131
<%- end -%>
3232
JsonapiSpecHelpers::Payload.register(:<%= file_name %>) do
33+
<%- attributes.each do |a| -%>
34+
<%- type = a.type == :boolean ? [TrueClass, FalseClass] : a.type.to_s.classify -%>
35+
key(:<%= a.name %>, <%= type %>)
36+
<%- end -%>
3337
end

lib/generators/jsonapi/templates/serializer.rb.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ class Serializable<%= class_name %> < JSONAPI::Serializable::Resource
1818
# @object.name.upcase
1919
# end
2020
<%- end -%>
21+
<%- attributes.each do |a| -%>
22+
attribute :<%= a.name %>
23+
<%- end -%>
2124
end
2225
<% end -%>

lib/generators/jsonapi/templates/show_request_spec.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ RSpec.describe "<%= file_name.pluralize %>#show", type: :request do
55
let!(:<%= file_name %>) { create(:<%= file_name %>) }
66

77
it 'serializes the resource correctly' do
8-
get "/api/v1/<%= file_name.pluralize %>/#{<%= file_name %>.id}"
8+
get "/<%= api_namespace %>/v1/<%= file_name.pluralize %>/#{<%= file_name %>.id}"
99

1010
assert_payload(:<%= file_name %>, <%= file_name %>, json_item)
1111
end

lib/generators/jsonapi/templates/update_request_spec.rb.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ RSpec.describe "<%= type %>#update", type: :request do
1919
# Replace 'xit' with 'it' after adding attributes
2020
xit 'updates the resource' do
2121
expect {
22-
jsonapi_put "/api/v1/<%= type %>/#{<%= file_name %>.id}", payload
22+
jsonapi_put "/<%= api_namespace %>/v1/<%= type %>/#{<%= file_name %>.id}", payload
2323
}.to change { <%= file_name %>.reload.attributes }
2424
assert_payload(:<%= file_name %>, <%= file_name %>, json_item)
2525

0 commit comments

Comments
 (0)