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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ <%- unless skip_swagger -%>
4
+ require 'grape-swagger'
5
+ <%- end -%>
6
+
7
+ module Api
8
+ module <%= wrapper.capitalize %>
9
+ class Controllers < Grape::API
10
+ <%- unless skip_swagger -%>
11
+
12
+ add_swagger_documentation(
13
+ api_version: "<%= wrapper.underscore %>",
14
+ hide_documentation_path: true,
15
+ mount_path: "/kriangle/api/<%= wrapper.underscore %>/swagger_doc",
16
+ hide_format: true
17
+ )
18
+ <%- end -%>
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ class Add<%= class_name.pluralize %>CountTo<%= @options[:belongs_to].classify.pluralize %> < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :<%= @options[:belongs_to].pluralize %>, :<%= class_name.pluralize.underscore %>_count, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class CreateAuthentications < ActiveRecord::Migration<%= "[#{Rails::VERSION::STRING[0..2]}]" if Rails::VERSION::MAJOR > 4 %>
2
+ def change
3
+ create_table :authentications do |t|
4
+ t.references :<%= underscored_user_class %>, foreign_key: true
5
+
6
+ t.text :client_id
7
+ t.text :token
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class CreateAvatars < ActiveRecord::Migration<%= "[#{Rails::VERSION::STRING[0..2]}]" if Rails::VERSION::MAJOR > 4 %>
2
+ def change
3
+ create_table :avatars do |t|
4
+ t.references :<%= underscored_user_class %>, foreign_key: true
5
+
6
+ t.text :image
7
+ t.integer :sorting, default: 0
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,48 @@
1
+ class Create<%= user_class.pluralize %> < ActiveRecord::Migration<%= "[#{Rails::VERSION::STRING[0..2]}]" if Rails::VERSION::MAJOR > 4 %>
2
+ def change
3
+ create_table :<%= user_class.pluralize.underscore %> do |t|
4
+ ## User Info
5
+ <%- for attribute in model_attributes -%>
6
+ <%- next if attribute.name == 'email' -%>
7
+ t.<%= attribute.type %> :<%= attribute.name %>
8
+ <%- end -%>
9
+
10
+ ## Database authenticatable
11
+ t.string :email, :null => false, :default => ""
12
+ t.string :encrypted_password, :null => false, :default => ""
13
+
14
+ ## Recoverable
15
+ t.string :reset_password_token
16
+ t.datetime :reset_password_sent_at
17
+ t.boolean :allow_password_change, :default => false
18
+
19
+ ## Rememberable
20
+ t.datetime :remember_created_at
21
+
22
+ ## Trackable
23
+ t.integer :sign_in_count, :default => 0, :null => false
24
+ t.datetime :current_sign_in_at
25
+ t.datetime :last_sign_in_at
26
+ t.string :current_sign_in_ip
27
+ t.string :last_sign_in_ip
28
+
29
+ ## Confirmable
30
+ t.string :confirmation_token
31
+ t.datetime :confirmed_at
32
+ t.datetime :confirmation_sent_at
33
+ t.string :unconfirmed_email # Only if using reconfirmable
34
+
35
+ ## Lockable
36
+ t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts
37
+ t.string :unlock_token # Only if unlock strategy is :email or :both
38
+ t.datetime :locked_at
39
+
40
+ t.timestamps
41
+ end
42
+
43
+ add_index :<%= user_class.pluralize.underscore %>, :email, unique: true
44
+ add_index :<%= user_class.pluralize.underscore %>, :reset_password_token, unique: true
45
+ add_index :<%= user_class.pluralize.underscore %>, :confirmation_token, unique: true
46
+ # add_index :<%= user_class.pluralize.underscore %>, :unlock_token, unique: true
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module CustomDescription
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def description(title)
9
+ desc title,
10
+ headers: {
11
+ 'X-Uid' => {
12
+ description: 'User Id',
13
+ required: true,
14
+ default: ENV['X_UID']
15
+ },
16
+ 'X-Client-Id' => {
17
+ description: 'Client Id',
18
+ required: true,
19
+ default: ENV['X_CLIENT_ID']
20
+ },
21
+ 'X-Authentication-Token' => {
22
+ description: 'Authentication Token',
23
+ required: true,
24
+ default: ENV['X_AUTH_TOKEN']
25
+ }
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Api
4
+ module <%= wrapper.capitalize %>
5
+ module Defaults
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ prefix "api"
10
+ version "<%= wrapper.underscore %>", using: :path
11
+ default_format :json
12
+ format :json
13
+ formatter :json, Grape::Formatter::ActiveModelSerializers
14
+
15
+ # Authenticator and Responder
16
+ <%- unless skip_authentication -%>
17
+ include Api::Authenticator
18
+ <%- end -%>
19
+ include Api::Responder
20
+
21
+ helpers do
22
+ def logger
23
+ Rails.logger
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Kriangle.setup do |config|
4
+ config.database = '<%= database %>'
5
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %> < ApplicationRecord
4
+ <%- for ma in model_associations.uniq { |ma| ma.association_name } -%>
5
+ <%= ma.association %>
6
+ <%- end -%>
7
+ <%- if self_reference -%>
8
+ belongs_to :<%= parent_association_name %>, :class_name => '<%= class_name %>', optional: true
9
+ has_many :<%= child_association_name %>, :class_name => '<%= class_name %>', :foreign_key => '<%= parent_association_name %>_id'
10
+
11
+ scope :only_parent, -> { includes(:<%= child_association_name %>).where(<%= parent_association_name %>_id: nil) }
12
+ <%- end -%>
13
+ <%- for polymorphic in @options[:polymorphics] -%>
14
+ belongs_to :<%= polymorphic %>, polymorphic: true
15
+ # use below into referenced model
16
+ # has_many :<%= polymorphic.gsub('able','').pluralize %>, as: :<%= polymorphic %>, dependent: :destroy
17
+ <%- end -%>
18
+ <%- for parent_model in @options[:references] -%>
19
+ <%- if !reference || parent_model != user_class -%>
20
+ belongs_to :<%= parent_model %>
21
+ <%- end -%>
22
+ <%- end -%>
23
+ <%- if database == 'sqlite3' -%>
24
+ <%- attributes.select { |a| a.type == 'array' }.each do |a| -%>
25
+ serialize :<%= a.name %>, Array
26
+ <%- end -%>
27
+ <%- end -%>
28
+ <%- if @options[:attributes].size != 0 -%>
29
+
30
+ # validation's on columns
31
+ validates :<%= @options[:attributes].join(', :') %>, presence: true
32
+ <%- end -%>
33
+ end
@@ -0,0 +1,33 @@
1
+ class Create<%= class_name.pluralize %> < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :<%= controller_file_name %> do |t|
4
+ <%- for ma in model_associations.select { |ma| ma.association_type == 'belongs_to' }.uniq { |ma| ma.association_name } -%>
5
+ <%- if ma.foreign_key.present? -%>
6
+ t.integer :<%= ma.foreign_key %>, index: true
7
+ <%- else -%>
8
+ t.references :<%= ma.association_name %><%= ", foreign_key: true" if ma.class_name.blank? %>
9
+ <%- end -%>
10
+ <%- end -%>
11
+ <%- if self_reference -%>
12
+ t.references :<%= parent_association_name %>, index: true
13
+ <%- end -%>
14
+ <%- for attribute in @polymorphics -%>
15
+ t.references :<%= attribute.name %>, polymorphic: true
16
+ <%- end -%>
17
+ <%- for attribute in @attributes -%>
18
+ <%- if attribute.type == 'array' -%>
19
+ <%- if database == 'sqlite3' -%>
20
+ t.text :<%= attribute.name %>
21
+ <%- else -%>
22
+ t.text :<%= attribute.name %>, array: true, default: []
23
+ <%- end -%>
24
+ <%- else -%>
25
+ t.<%= attribute.type || 'string' %> :<%= attribute.name %><%= ", default: #{attribute.default.gsub('~', "'")}" unless attribute.default.nil? %>
26
+ <%- end -%>
27
+ <%- end -%>
28
+ <%- unless skip_timestamps -%>
29
+ t.timestamps
30
+ <%- end -%>
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ class JsonResponse
4
+ attr_reader :success, :message, :data, :meta, :errors
5
+
6
+ def initialize(options = {})
7
+ @success = options[:success].to_s.empty? ? true : options[:success]
8
+ @message = options[:message] || options[:errors].try(:first) || ''
9
+ @data = options[:data] || []
10
+ @meta = options[:meta] || {}
11
+ @errors = options[:errors] || []
12
+ end
13
+
14
+ def as_json(*)
15
+ {
16
+ success: success,
17
+ message: message,
18
+ data: data,
19
+ meta: meta,
20
+ errors: errors
21
+ }
22
+ end
23
+ end
24
+
25
+ module Api
26
+ module Responder
27
+ extend ActiveSupport::Concern
28
+
29
+ included do
30
+ # catch exception and return JSON-formatted error
31
+ # def handle_exceptions
32
+ # begin
33
+ # yield
34
+ # rescue <%= get_record_not_found_exception %> => e
35
+ # status_code = 404
36
+ # rescue <%= get_record_invalid_exception %> => e
37
+ # json_error_response(e.record) && return
38
+ # rescue ArgumentError => e
39
+ # status_code = 400
40
+ # rescue StandardError => e
41
+ # status_code = 500
42
+ # end
43
+ # json_error_response({ message: e.class.to_s, errors: [{ detail: e.message, trace: e.backtrace }] }, status_code) unless e.class == NilClass
44
+ # end
45
+
46
+ helpers do
47
+ # extract options
48
+ # i.e. serializer = nil, options = {}, additional_response = {}
49
+ def extract_options resource, options, collection = false
50
+ # identify serializer
51
+ @serializer = options[:serializer]
52
+ unless @serializer
53
+ if collection
54
+ class_name = resource&.first&.class
55
+ if class_name.present? && class_name != NilClass
56
+ @serializer = "#{class_name}Serializer".constantize
57
+ end
58
+ else
59
+ @serializer = "#{resource.class}Serializer".constantize
60
+ end
61
+ end
62
+
63
+ # additional params
64
+ @serializer_options = options[:serializer_options] || {}
65
+ @additional_response = options[:additional_response] || {}
66
+ end
67
+
68
+ # render single object with serializer
69
+ def render_object object, **options
70
+ extract_options(object, options) # extract required options
71
+
72
+ json_success_response({
73
+ data: @serializer.present? ? single_serializer.new(object, serializer: @serializer, serializer_options: @serializer_options) : {}
74
+ }.merge(@additional_response))
75
+ end
76
+
77
+ def format_aggregation(aggs)
78
+ return [] if aggs.blank?
79
+
80
+ aggregations = []
81
+ aggs.each do |k, value|
82
+ value['buckets'].each do |bucket|
83
+ bucket['count'] = bucket.delete('doc_count')
84
+ end
85
+ aggregations << { name: k, buckets: value['buckets'] }
86
+ end
87
+ aggregations
88
+ end
89
+
90
+ # render collection of objects with serializer
91
+ def render_objects objects, **options
92
+ extract_options(objects.to_a, options, true) # extract required options
93
+
94
+ # collect meta data if any present there
95
+ meta = options[:extra_params].present? ? options[:extra_params] : {}
96
+ meta[:suggestions] = objects.suggestions if objects.respond_to?(:suggestions) && objects.suggestions.present?
97
+ meta[:aggregations] = format_aggregation(objects.aggs) if objects.respond_to?(:aggs)
98
+ if objects.respond_to?(:total_count)
99
+ meta[:pagination] = {
100
+ total_count: objects.total_count,
101
+ current_page: objects.current_page,
102
+ next_page: objects.next_page,
103
+ per_page: objects.try(:per_page) || objects.try(:limit_value) || 10000
104
+ }
105
+ end
106
+
107
+ # send data & meta
108
+ json_success_response({
109
+ data: @serializer.present? ? array_serializer.new(objects, serializer: @serializer, serializer_options: @serializer_options) : [],
110
+ meta: meta
111
+ }.merge(@additional_response))
112
+ end
113
+
114
+ def render_not_found(resource)
115
+ render_error_response(["#{ resource } not found."], :not_found)
116
+ end
117
+
118
+ def render_errors(object)
119
+ render_error_response(object.errors, :unprocessable_entity)
120
+ end
121
+
122
+ def render_error
123
+ render_error_response(['Something went wrong. Please try after sometime.'], :unprocessable_entity)
124
+ end
125
+
126
+ def render_interval_server_error
127
+ render_error_response(['Internal server error.'], 500)
128
+ end
129
+
130
+ def render_unprocessable_entity(errors)
131
+ render_error_response(errors, 422) and return
132
+ end
133
+
134
+ def render_unauthorized_access
135
+ render_error_response(['Invalid or expired token.'], 401) and return
136
+ end
137
+
138
+ def render_error_response(errors = [], status = 422)
139
+ json_error_response({ errors: errors }, status)
140
+ end
141
+
142
+ def json_success_response response = {}
143
+ JsonResponse.new(response.merge(status: true)).as_json
144
+ end
145
+
146
+ def json_error_response response = {}, status = 422
147
+ error!(JsonResponse.new(response.merge(status: true)).as_json, status)
148
+ end
149
+
150
+ def array_serializer
151
+ ActiveModel::Serializer::CollectionSerializer
152
+ end
153
+
154
+ def single_serializer
155
+ ActiveModelSerializers::SerializableResource
156
+ end
157
+ end
158
+
159
+ rescue_from <%= get_record_not_found_exception %> do |e|
160
+ message = e.try(:problem) || e.try(:message)
161
+ model_name = message.match(/(?<=class|find)[^w]+/)&.to_s&.strip
162
+ json_error_response(errors: ["No #{model_name || 'Record'} Found."], status: 404)
163
+ end
164
+
165
+ rescue_from <%= get_record_invalid_exception %> do |e|
166
+ json_error_response(errors: [e.message], status: 422)
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= @options[:class_name] || class_name %>Serializer < ActiveSerializer
4
+ attributes :<%= @options[:attributes].join(', :') %>
5
+ <%- unless skip_tips -%>
6
+ # attributes :custom_function
7
+ <%- end -%>
8
+
9
+ <%- @options[:references].try(:each) do |parent_model| -%>
10
+ belongs_to :<%= parent_model %>
11
+ <%- end -%>
12
+ <%- for ma in model_associations -%>
13
+ <%= ma.association_type_with_name %>
14
+ <%- end -%>
15
+ <%- if self_reference -%>
16
+ has_many :<%= child_association_name %>, serializer: <%= @options[:class_name] || class_name %>Serializer
17
+ <%- end -%>
18
+ <%- unless skip_tips -%>
19
+ # association's example
20
+ # belongs_to :user
21
+ # has_one :address
22
+ # has_many :avatars
23
+
24
+ # def custom_function
25
+ # object.association(:association_model_name).loaded? ? object.association_model_name : {}
26
+ # end
27
+
28
+ # add your custom attributes here if required
29
+ # def attributes(*args)
30
+ # hash = super
31
+ # # hash[:your_key] = object.some_method_call
32
+ # hash
33
+ # end
34
+ <%- end -%>
35
+ end