kriangle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +30 -0
  5. data/Gemfile.lock +216 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +307 -0
  8. data/Rakefile +4 -0
  9. data/bin/console +15 -0
  10. data/bin/setup +8 -0
  11. data/kriangle.gemspec +42 -0
  12. data/lib/generators/kriangle/generator_helpers.rb +95 -0
  13. data/lib/generators/kriangle/install_generator.rb +149 -0
  14. data/lib/generators/kriangle/module_generator.rb +273 -0
  15. data/lib/generators/kriangle/templates/active_serializer.rb +9 -0
  16. data/lib/generators/kriangle/templates/application_record.rb +9 -0
  17. data/lib/generators/kriangle/templates/auth.rb +138 -0
  18. data/lib/generators/kriangle/templates/authentication.rb +7 -0
  19. data/lib/generators/kriangle/templates/authenticator.rb +70 -0
  20. data/lib/generators/kriangle/templates/avatar.rb +5 -0
  21. data/lib/generators/kriangle/templates/avatar_uploader.rb +49 -0
  22. data/lib/generators/kriangle/templates/base.rb +6 -0
  23. data/lib/generators/kriangle/templates/controller.rb +241 -0
  24. data/lib/generators/kriangle/templates/controllers.rb +21 -0
  25. data/lib/generators/kriangle/templates/counter_cache_migration.rb +5 -0
  26. data/lib/generators/kriangle/templates/create_authentications.rb +10 -0
  27. data/lib/generators/kriangle/templates/create_avatars.rb +10 -0
  28. data/lib/generators/kriangle/templates/create_users.rb.erb +48 -0
  29. data/lib/generators/kriangle/templates/custom_description.rb +30 -0
  30. data/lib/generators/kriangle/templates/defaults.rb +29 -0
  31. data/lib/generators/kriangle/templates/kriangle.rb +5 -0
  32. data/lib/generators/kriangle/templates/model.rb +33 -0
  33. data/lib/generators/kriangle/templates/module_migration.rb +33 -0
  34. data/lib/generators/kriangle/templates/responder.rb +170 -0
  35. data/lib/generators/kriangle/templates/serializer.rb +35 -0
  36. data/lib/generators/kriangle/templates/swagger.rb +8 -0
  37. data/lib/generators/kriangle/templates/user.rb +35 -0
  38. data/lib/kriangle.rb +36 -0
  39. data/lib/kriangle/version.rb +5 -0
  40. metadata +355 -0
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveSerializer < ActiveModel::Serializer
4
+ <%- if custom_orm == 'Mongoid' -%>
5
+ def id
6
+ object._id.to_s
7
+ end
8
+ <%- end -%>
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ <% if custom_orm == 'Mongoid' %>
4
+ class ApplicationRecord
5
+ <% else %>
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ <% end %>
9
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module <%= wrapper.capitalize %>
5
+ class <%= controller_path %> < Grape::API
6
+ include Api::<%= wrapper.capitalize %>::Defaults
7
+
8
+ resource :<%= controller_path.underscore %> do
9
+ include Api::CustomDescription
10
+
11
+ desc "Register new <%= underscored_user_class %>"
12
+ params do
13
+ requires :<%= underscored_user_class %>, type: Hash do
14
+ requires :email, type: String, desc: "Email address"
15
+ requires :password, type: String, desc: "Password"
16
+ requires :password_confirmation, type: String, desc: "Password Confirmation"
17
+ # Additional(optional) parameters
18
+ <%- for attribute in model_attributes -%>
19
+ <% next if attribute.name == 'email' %>
20
+ <%- if attribute.name == 'gender' -%>
21
+ <%= require_or_optional(attribute) %> :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>", default: 'Male', values: ['Male', 'Female', 'Other']
22
+ <%- else -%>
23
+ <%= require_or_optional(attribute) %> :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>"
24
+ <%- end -%>
25
+ <%- end -%>
26
+ end
27
+ end
28
+ post :register do
29
+ <%= underscored_user_class %> = <%= user_class %>.new(params[:<%= underscored_user_class %>])
30
+ if <%= underscored_user_class %>.save
31
+ create_authentication(<%= underscored_user_class %>)
32
+ render_object(<%= underscored_user_class %>, additional_response: { message: "You have registered successfully." })
33
+ else
34
+ json_error_response({ errors: <%= underscored_user_class %>.errors.full_messages })
35
+ end
36
+ end
37
+
38
+ desc "Creates and returns <%= underscored_user_class %> with access token if valid login"
39
+ params do
40
+ requires :<%= underscored_user_class %>, type: Hash do
41
+ requires :email, type: String, desc: "Email address"
42
+ requires :password, type: String, desc: "Password"
43
+ end
44
+ end
45
+ post :login do
46
+ <%= underscored_user_class %> = <%= user_class %>.find_by(email: params[:<%= underscored_user_class %>][:email].downcase)
47
+ if <%= underscored_user_class %> && <%= underscored_user_class %>.valid_password?(params[:<%= underscored_user_class %>][:password])
48
+ create_authentication(<%= underscored_user_class %>)
49
+ render_object(<%= underscored_user_class %>, additional_response: { message: "You have successfully logged in." })
50
+ else
51
+ json_error_response({ errors: ['Invalid email or password.'] }, 401)
52
+ end
53
+ end
54
+
55
+ description "Logout <%= underscored_user_class %>"
56
+ post :logout do
57
+ destroy_authentication_token
58
+ json_success_response(message: "You have successfully logout.")
59
+ end
60
+
61
+ description "Return pong if logged in correctly"
62
+ get :ping do
63
+ authenticate!
64
+ json_success_response(message: "pong")
65
+ end
66
+
67
+ desc "Forgot Password"
68
+ params do
69
+ requires :<%= underscored_user_class %>, type: Hash do
70
+ requires :email, type: String, desc: "Email address"
71
+ end
72
+ end
73
+ post :forgot_password do
74
+ <%= underscored_user_class %> = <%= user_class %>.find_by(email: params[:<%= underscored_user_class %>][:email].downcase)
75
+ if <%= underscored_user_class %>.present?
76
+ <%= underscored_user_class %>.update(reset_token: token)
77
+ # send Forgot Password email
78
+ json_success_response(message: "You will receive email with instructions to reset password shortly.")
79
+ else
80
+ json_error_response({ errors: ['Invalid email address.'] })
81
+ end
82
+ end
83
+
84
+ desc "Reset Password"
85
+ params do
86
+ requires :reset_token, type: String, desc: "Reset Password"
87
+ requires :<%= underscored_user_class %>, type: Hash do
88
+ requires :password, type: String, desc: "Password"
89
+ requires :password_confirmation, type: String, desc: "Password Confirmation"
90
+ end
91
+ end
92
+ post :reset_password do
93
+ <%= underscored_user_class %> = <%= user_class %>.find_by(reset_token: params[:reset_token])
94
+ if <%= underscored_user_class %>.update(params[:<%= underscored_user_class %>])
95
+ # send Reset Password email
96
+ json_success_response(message: "Your password have successfully changed.")
97
+ else
98
+ json_error_response({ errors: ['Invalid reset token.'] })
99
+ end
100
+ end
101
+
102
+ description "Return <%= underscored_user_class %>"
103
+ get '' do
104
+ authenticate!
105
+ render_object(current_<%= underscored_user_class %>)
106
+ end
107
+
108
+ description "Update <%= underscored_user_class %>"
109
+ params do
110
+ requires :<%= underscored_user_class %>, type: Hash do
111
+ # Additional(optional) parameters
112
+ <%- for attribute in model_attributes -%>
113
+ <% next if attribute.name == 'email' %>
114
+ <%- if attribute.name == 'gender' -%>
115
+ <%= require_or_optional(attribute) %> :<%= attribute.name %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>", default: 'Male', values: ['Male', 'Female', 'Other']
116
+ <%- else -%>
117
+ <%= require_or_optional(attribute) %> :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>"
118
+ <%- end -%>
119
+ <%- end -%>
120
+ # group :avatars_attributes, type: Hash, desc: "An array of avatars" do
121
+ # optional :id, type: Integer
122
+ # optional :image, type: String
123
+ # optional :_destroy, type: Boolean
124
+ # end
125
+ end
126
+ end
127
+ put "" do
128
+ authenticate!
129
+ if current_<%= underscored_user_class %>.update(params[:<%= underscored_user_class %>])
130
+ render_object(current_<%= underscored_user_class %>)
131
+ else
132
+ json_error_response({ errors: current_<%= underscored_user_class %>.errors.full_messages })
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Authentication < ApplicationRecord
4
+ belongs_to :<%= underscored_user_class %>
5
+
6
+ validates :user_id, :client_id, :token, presence: true
7
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bcrypt'
4
+
5
+ module Api
6
+ module Authenticator
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ helpers do
11
+ def generate_random_string
12
+ "#{SecureRandom.urlsafe_base64}#{DateTime.now.to_i}#{SecureRandom.urlsafe_base64}"
13
+ end
14
+
15
+ def create_authentication(<%= underscored_user_class %>, client_id = ENV['CLIENT_ID'])
16
+ # delete all old tokens if any present
17
+ <%= underscored_user_class %>.authentications.delete_all
18
+
19
+ # create new auth token
20
+ client_id ||= SecureRandom.urlsafe_base64(nil, false)
21
+ token = generate_random_string
22
+ authentication = <%= underscored_user_class %>.authentications.create(client_id: client_id, token: BCrypt::Password.create(token))
23
+
24
+ # build auth header
25
+ header 'X-Uid', authentication.<%= underscored_user_class %>_id
26
+ header 'X-Client-Id', authentication.client_id
27
+ header 'X-Authentication-Token', token
28
+ end
29
+
30
+ def authentication
31
+ # user has already been found and authenticated
32
+ return @authentication if @authentication
33
+
34
+ # get details from header or params
35
+ uid = headers['X-Uid'] || params['uid']
36
+ @token ||= headers['X-Authentication-Token'] || params['access-token']
37
+ @client_id ||= request.headers['X-Client-Id'] || params['client-id']
38
+
39
+ # client_id isn't required, set to 'default' if absent
40
+ @client_id ||= 'default'
41
+
42
+ # ensure we clear the client_id
43
+ unless @token
44
+ @client_id = nil
45
+ return
46
+ end
47
+
48
+ return unless @token
49
+
50
+ auth = Authentication.where(<%= underscored_user_class %>_id: uid, client_id: @client_id).last || return
51
+ return @authentication = auth if ::BCrypt::Password.new(auth.token) == @token
52
+
53
+ @authentication = nil
54
+ end
55
+
56
+ def destroy_authentication_token
57
+ authentication&.destroy
58
+ end
59
+
60
+ def current_<%= underscored_user_class %>
61
+ @current_<%= underscored_user_class %> ||= authentication&.<%= underscored_user_class %>
62
+ end
63
+
64
+ def authenticate!
65
+ render_unauthorized_access && return unless current_<%= underscored_user_class %>
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,5 @@
1
+ class Avatar < ApplicationRecord
2
+ belongs_to :<%= underscored_user_class %>
3
+
4
+ mount_uploader :image, AvatarUploader
5
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AvatarUploader < CarrierWave::Uploader::Base
4
+ # Include RMagick or MiniMagick support:
5
+ # include CarrierWave::RMagick
6
+ # include CarrierWave::MiniMagick
7
+
8
+ # Choose what kind of storage to use for this uploader:
9
+ storage :file
10
+ # storage :fog
11
+
12
+ # Override the directory where uploaded files will be stored.
13
+ # This is a sensible default for uploaders that are meant to be mounted:
14
+ def store_dir
15
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
16
+ end
17
+
18
+ # Provide a default URL as a default if there hasn't been a file uploaded:
19
+ # def default_url(*args)
20
+ # # For Rails 3.1+ asset pipeline compatibility:
21
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
22
+ #
23
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
24
+ # end
25
+
26
+ # Process files as they are uploaded:
27
+ # process scale: [200, 300]
28
+ #
29
+ # def scale(width, height)
30
+ # # do something
31
+ # end
32
+
33
+ # Create different versions of your uploaded files:
34
+ # version :thumb do
35
+ # process resize_to_fit: [50, 50]
36
+ # end
37
+
38
+ # Add a white list of extensions which are allowed to be uploaded.
39
+ # For images you might use something like this:
40
+ # def extension_whitelist
41
+ # %w(jpg jpeg gif png)
42
+ # end
43
+
44
+ # Override the filename of the uploaded files:
45
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
46
+ # def filename
47
+ # "something.jpg" if original_filename
48
+ # end
49
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ class Base < Grape::API
5
+ end
6
+ end
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module <%= wrapper.capitalize %>
5
+ class <%= controller_path %> < Grape::API
6
+ include Api::<%= wrapper.capitalize %>::Defaults
7
+
8
+ resource :<%= controller_path.underscore %> do
9
+ <%- unless skip_authentication -%>
10
+ include Api::CustomDescription
11
+
12
+ before do
13
+ authenticate!
14
+ end
15
+ <%- end -%>
16
+ <%- if resources -%>
17
+ <%- if controller_actions.include?('index') -%>
18
+
19
+ <%= description_method_name %> "Return all <%= plural_name %>"
20
+ <%- if !skip_pagination || search_by -%>
21
+ <%- if !reference || (reference && association_type == 'has_many') || reference_id_param -%>
22
+ params do
23
+ <%- if search_by -%>
24
+ optional :q, type: Hash do
25
+ optional :m, type: String, desc: 'Matching case', default: 'or', values: ['or', 'and']
26
+ <%- for attribute in model_attributes.select { |ma| ma.search_by.present? } -%>
27
+ optional :<%= attribute.name %><%= attribute.search_by %>, type: <%= get_attribute_type(attribute.type) %>, desc: "Search by <%= attribute.name.titleize %>"
28
+ <%- end -%>
29
+ end
30
+ <%- end -%>
31
+ <%- if reference_id_param -%>
32
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
33
+ <%- end -%>
34
+ <%- if !skip_pagination && (!reference || (reference && association_type == 'has_many')) -%>
35
+ optional :page, type: Integer, desc: "Page number", default: 0
36
+ optional :per_page, type: Integer, desc: "Per Page", default: 15
37
+ <%- end -%>
38
+ end
39
+ <%- end -%>
40
+ <%- end -%>
41
+ get "", root: :<%= plural_name %> do
42
+ <%- if reference -%>
43
+ <%- if association_type == 'has_many' -%>
44
+ <%- if search_by -%>
45
+ @q = <%= reference_name %>.<%= plural_name %><%= additional_where_clause %>.ransack(params[:q])
46
+ results = @q.result(distinct: true)
47
+ <%- else -%>
48
+ results = <%= reference_name %>.<%= plural_name %><%= additional_where_clause %>
49
+ <%- end -%>
50
+ <%- else -%>
51
+ <%= singular_name %> = <%= reference_name %>.<%= singular_name %> || raise(<%= get_record_not_found_exception %>)
52
+ render_object(<%= singular_name %>)
53
+ <%- end -%>
54
+ <%- else -%>
55
+ <%- if search_by -%>
56
+ @q = <%= class_name %><%= additional_where_clause %>.ransack(params[:q])
57
+ results = @q.result(distinct: true)
58
+ <%- else -%>
59
+ results = <%= class_name %><%= additional_where_clause %>.all
60
+ <%- end -%>
61
+ <%- end -%>
62
+ <%- if !reference || association_type == 'has_many' -%>
63
+ <%- if skip_pagination -%>
64
+ render_objects(results)
65
+ <%- else -%>
66
+ render_objects(paginate results)
67
+ <%- end -%>
68
+ <%- end -%>
69
+ end
70
+ <%- end -%>
71
+ <%- end -%>
72
+ <%- if controller_actions.include?('show') -%>
73
+
74
+ <%= description_method_name %> "Return a <%= singular_name %>"
75
+ <%- if reference_id_param -%>
76
+ params do
77
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
78
+ end
79
+ <%- end -%>
80
+ get ":id", root: "<%= singular_name %>" do
81
+ <%- if reference -%>
82
+ <%- if association_type == 'has_many' -%>
83
+ <%= singular_name %> = <%= reference_name %>.<%= plural_name %>.find(params[:id])
84
+ <%- else -%>
85
+ <%= singular_name %> = <%= reference_name %>.<%= singular_name %> || raise(<%= get_record_not_found_exception %>)
86
+ <%- end -%>
87
+ <%- else -%>
88
+ <%= singular_name %> = <%= class_name %>.find(params[:id])
89
+ <%- end -%>
90
+ render_object(<%= singular_name %>)
91
+ end
92
+ <%- end -%>
93
+ <%- if controller_actions.include?('create') -%>
94
+
95
+ <%= description_method_name %> "Create a <%= singular_name %>"
96
+ params do
97
+ <%- if reference_id_param -%>
98
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
99
+ <%- end -%>
100
+ requires :<%= singular_name %>, type: Hash do
101
+ <%- if self_reference -%>
102
+ optional :<%= parent_association_name %>_id, type: Integer, desc: "<%= class_name.classify %>'s id as parent"
103
+ <%- end -%>
104
+ <%- for attribute in model_associations.select { |ma| ma.association_type == 'belongs_to' && !ma.class_name && !ma.reference } -%>
105
+ <%= require_or_optional(attribute) %> :<%= attribute.association_name + '_id' %>, type: Integer, desc: "<%= attribute.association_name.titleize %>"
106
+ <%- end -%>
107
+ <%- for attribute in model_attributes -%>
108
+ <%= require_or_optional(attribute) %> :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>"
109
+ <%- end -%>
110
+ <%- unless skip_tips -%>
111
+ # requires :title, type: String, desc: "Title of the <%= singular_name %>"
112
+ # requires :content, type: String, desc: "Content of the <%= singular_name %>"
113
+ <%- end -%>
114
+ end
115
+ end
116
+ post "", root: "<%= singular_name %>" do
117
+ <%- if reference -%>
118
+ <%- if association_type == 'has_many' -%>
119
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= plural_name %>.<%= creation_method %>(params[:<%= singular_name %>])
120
+ <%- else -%>
121
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= singular_name %> || <%= reference_name_create_update %>.build_<%= singular_name %>(params[:<%= singular_name %>])
122
+ <%= singular_name %>.attributes = params[:<%= singular_name %>] if <%= singular_name %>.persisted?
123
+ <%- end -%>
124
+ <%- else -%>
125
+ <%= singular_name %> = <%= class_name %>.<%= creation_method %>(params[:<%= singular_name %>])
126
+ <%- end -%>
127
+ if <%= singular_name %>.save
128
+ render_object(<%= singular_name %>, additional_response: { message: "<%= class_name %> created successfully." })
129
+ else
130
+ json_error_response(errors: <%= singular_name %>.errors.full_messages)
131
+ end
132
+ end
133
+ <%- end -%>
134
+ <%- if controller_actions.include?('update') -%>
135
+
136
+ <%= description_method_name %> "Update a <%= singular_name %>"
137
+ params do
138
+ <%- if reference_id_param -%>
139
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
140
+ <%- end -%>
141
+ requires :<%= singular_name %>, type: Hash do
142
+ <%- for attribute in model_attributes -%>
143
+ optional :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>"
144
+ <%- end -%>
145
+ <%- unless skip_tips -%>
146
+ # requires :title, type: String, desc: "Title of the <%= singular_name %>"
147
+ # requires :content, type: String, desc: "Content of the <%= singular_name %>"
148
+ # optional :views, type: String, desc: "Content of the <%= singular_name %>"
149
+ <%- end -%>
150
+ end
151
+ end
152
+ put ":id", root: "<%= singular_name %>" do
153
+ <%- if reference -%>
154
+ <%- if association_type == 'has_many' -%>
155
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= plural_name %>.find(params[:id])
156
+ <%- else -%>
157
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= singular_name %> || raise(<%= get_record_not_found_exception %>)
158
+ <%- end -%>
159
+ <%- else -%>
160
+ <%= singular_name %> = <%= class_name %>.find(params[:id])
161
+ <%- end -%>
162
+ if <%= singular_name %>.update(params[:<%= singular_name %>])
163
+ render_object(<%= singular_name %>, additional_response: { message: "<%= class_name %> updated successfully." })
164
+ else
165
+ json_error_response(errors: <%= singular_name %>.errors.full_messages)
166
+ end
167
+ end
168
+ <%- end -%>
169
+ <%- if controller_actions.include?('destroy') -%>
170
+
171
+ <%= description_method_name %> "Destroy a <%= singular_name %>"
172
+ <%- if reference_id_param -%>
173
+ params do
174
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
175
+ end
176
+ <%- end -%>
177
+ delete ":id", root: "<%= singular_name %>" do
178
+ <%- if reference -%>
179
+ <%- if association_type == 'has_many' -%>
180
+ <%= singular_name %> = <%= reference_name %>.<%= plural_name %>.find(params[:id])
181
+ <%- else -%>
182
+ <%= singular_name %> = <%= reference_name %>.<%= singular_name %> || raise(<%= get_record_not_found_exception %>)
183
+ <%- end -%>
184
+ <%- else -%>
185
+ <%= singular_name %> = <%= class_name %>.find(params[:id])
186
+ <%- end -%>
187
+ if <%= singular_name %>.destroy
188
+ json_success_response(message: "<%= class_name %> destroyed successfully.")
189
+ else
190
+ json_error_response(errors: <%= singular_name %>.errors.full_messages)
191
+ end
192
+ end
193
+ <%- end -%>
194
+ <%- if controller_actions.include?('create_or_destroy') -%>
195
+
196
+ <%= description_method_name %> "Create or Destroy a <%= singular_name %>"
197
+ params do
198
+ <%- if reference_id_param -%>
199
+ requires :<%= reference_id_param %>, type: Integer, desc: "<%= @user_class.classify %>'s id"
200
+ <%- end -%>
201
+ requires :<%= singular_name %>, type: Hash do
202
+ <%- if self_reference -%>
203
+ optional :<%= parent_association_name %>_id, type: Integer, desc: "<%= class_name.classify %>'s id as parent"
204
+ <%- end -%>
205
+ <%- for attribute in model_associations.select { |ma| ma.association_type == 'belongs_to' && !ma.class_name && !ma.reference } -%>
206
+ <%= require_or_optional(attribute) %> :<%= attribute.association_name + '_id' %>, type: Integer, desc: "<%= attribute.association_name.titleize %>"
207
+ <%- end -%>
208
+ <%- for attribute in model_attributes -%>
209
+ <%= require_or_optional(attribute) %> :<%= get_attribute_name(attribute.name, attribute.type) %>, type: <%= get_attribute_type(attribute.type) %>, desc: "<%= attribute.name.titleize %>"
210
+ <%- end -%>
211
+ end
212
+ end
213
+ post "", root: "<%= singular_name %>" do
214
+ <%- if reference -%>
215
+ <%- if association_type == 'has_many' -%>
216
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= plural_name %>.find_or_initialize_by(params[:<%= singular_name %>])
217
+ <%- else -%>
218
+ <%= singular_name %> = <%= reference_name_create_update %>.<%= singular_name %> || <%= reference_name_create_update %>.build_<%= singular_name %>(params[:<%= singular_name %>])
219
+ <%- end -%>
220
+ <%- else -%>
221
+ <%= singular_name %> = <%= class_name %>.find_or_initialize_by(params[:<%= singular_name %>])
222
+ <%- end -%>
223
+ if <%= singular_name %>.persisted?
224
+ if <%= singular_name %>.destroy_all
225
+ json_success_response(message: "<%= class_name %> destroyed successfully.")
226
+ else
227
+ json_error_response(errors: <%= singular_name %>.errors.full_messages)
228
+ end
229
+ else
230
+ if <%= singular_name %>.save
231
+ render_object(<%= singular_name %>, additional_response: { message: "<%= class_name %> created successfully." })
232
+ else
233
+ json_error_response(errors: <%= singular_name %>.errors.full_messages)
234
+ end
235
+ end
236
+ end
237
+ <%- end -%>
238
+ end
239
+ end
240
+ end
241
+ end