ditty 0.8.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/.env.test +2 -0
  3. data/.gitignore +3 -0
  4. data/.pryrc +2 -0
  5. data/.rubocop.yml +23 -4
  6. data/.travis.yml +4 -8
  7. data/CNAME +1 -0
  8. data/Dockerfile +18 -0
  9. data/Gemfile.ci +0 -17
  10. data/Rakefile +2 -2
  11. data/_config.yml +1 -0
  12. data/config.ru +4 -4
  13. data/ditty.gemspec +28 -18
  14. data/docs/CNAME +1 -0
  15. data/docs/_config.yml +1 -0
  16. data/docs/index.md +34 -0
  17. data/exe/ditty +2 -0
  18. data/lib/ditty/cli.rb +41 -5
  19. data/lib/ditty/components/{app.rb → ditty.rb} +18 -16
  20. data/lib/ditty/controllers/{application.rb → application_controller.rb} +63 -34
  21. data/lib/ditty/controllers/{audit_logs.rb → audit_logs_controller.rb} +4 -2
  22. data/lib/ditty/controllers/{auth.rb → auth_controller.rb} +22 -18
  23. data/lib/ditty/controllers/{component.rb → component_controller.rb} +23 -20
  24. data/lib/ditty/controllers/{main.rb → main_controller.rb} +6 -2
  25. data/lib/ditty/controllers/roles_controller.rb +23 -0
  26. data/lib/ditty/controllers/user_login_traits_controller.rb +46 -0
  27. data/lib/ditty/controllers/{users.rb → users_controller.rb} +13 -11
  28. data/lib/ditty/db.rb +7 -5
  29. data/lib/ditty/emails/base.rb +37 -32
  30. data/lib/ditty/generators/crud_generator.rb +114 -0
  31. data/lib/ditty/generators/migration_generator.rb +26 -0
  32. data/lib/ditty/generators/project_generator.rb +52 -0
  33. data/lib/ditty/helpers/component.rb +2 -1
  34. data/lib/ditty/helpers/pundit.rb +24 -8
  35. data/lib/ditty/helpers/response.rb +15 -13
  36. data/lib/ditty/helpers/views.rb +28 -6
  37. data/lib/ditty/listener.rb +6 -4
  38. data/lib/ditty/memcached.rb +8 -0
  39. data/lib/ditty/middleware/accept_extension.rb +2 -2
  40. data/lib/ditty/middleware/error_catchall.rb +2 -2
  41. data/lib/ditty/models/base.rb +9 -0
  42. data/lib/ditty/models/identity.rb +11 -7
  43. data/lib/ditty/models/role.rb +1 -0
  44. data/lib/ditty/models/user.rb +23 -2
  45. data/lib/ditty/policies/role_policy.rb +1 -1
  46. data/lib/ditty/policies/user_login_trait_policy.rb +1 -1
  47. data/lib/ditty/policies/user_policy.rb +1 -1
  48. data/lib/ditty/services/authentication.rb +27 -16
  49. data/lib/ditty/services/email.rb +19 -15
  50. data/lib/ditty/services/logger.rb +26 -20
  51. data/lib/ditty/services/pagination_wrapper.rb +7 -5
  52. data/lib/ditty/services/settings.rb +7 -6
  53. data/lib/ditty/tasks/ditty.rake +19 -1
  54. data/lib/ditty/tasks/omniauth-ldap.rake +2 -2
  55. data/lib/ditty/templates/.gitignore +5 -0
  56. data/lib/ditty/templates/.rspec +2 -0
  57. data/lib/ditty/templates/.rubocop.yml +7 -0
  58. data/lib/ditty/templates/Rakefile +12 -0
  59. data/lib/ditty/templates/application.rb +12 -0
  60. data/lib/ditty/templates/config.ru +37 -0
  61. data/lib/ditty/templates/controller.rb.erb +64 -0
  62. data/lib/ditty/templates/env.example +4 -0
  63. data/lib/ditty/templates/lib/project.rb.erb +5 -0
  64. data/lib/ditty/templates/logs/.empty_directory +0 -0
  65. data/lib/ditty/templates/migration.rb.erb +7 -0
  66. data/lib/ditty/templates/model.rb.erb +26 -0
  67. data/lib/ditty/templates/pids/.empty_directory +0 -0
  68. data/lib/ditty/templates/policy.rb.erb +48 -0
  69. data/{public → lib/ditty/templates/public}/browserconfig.xml +0 -0
  70. data/lib/ditty/templates/public/css/sb-admin-2.min.css +10 -0
  71. data/lib/ditty/templates/public/css/styles.css +13 -0
  72. data/lib/ditty/templates/public/favicon.ico +0 -0
  73. data/{public → lib/ditty/templates/public}/images/apple-icon.png +0 -0
  74. data/{public → lib/ditty/templates/public}/images/favicon-16x16.png +0 -0
  75. data/{public → lib/ditty/templates/public}/images/favicon-32x32.png +0 -0
  76. data/{public → lib/ditty/templates/public}/images/launcher-icon-1x.png +0 -0
  77. data/{public → lib/ditty/templates/public}/images/launcher-icon-2x.png +0 -0
  78. data/{public → lib/ditty/templates/public}/images/launcher-icon-4x.png +0 -0
  79. data/{public → lib/ditty/templates/public}/images/mstile-150x150.png +0 -0
  80. data/{public → lib/ditty/templates/public}/images/safari-pinned-tab.svg +0 -0
  81. data/lib/ditty/templates/public/js/sb-admin-2.min.js +7 -0
  82. data/lib/ditty/templates/public/js/scripts.js +1 -0
  83. data/{public/manifest.json → lib/ditty/templates/public/manifest.json.erb} +2 -2
  84. data/lib/ditty/templates/settings.yml.erb +29 -0
  85. data/lib/ditty/templates/sidekiq.rb +18 -0
  86. data/lib/ditty/templates/sidekiq.yml +9 -0
  87. data/lib/ditty/templates/spec_helper.rb +43 -0
  88. data/lib/ditty/templates/type.rb.erb +21 -0
  89. data/lib/ditty/templates/views/display.haml.tt +20 -0
  90. data/lib/ditty/templates/views/edit.haml.tt +10 -0
  91. data/lib/ditty/templates/views/form.haml.tt +11 -0
  92. data/lib/ditty/templates/views/index.haml.tt +29 -0
  93. data/lib/ditty/templates/views/new.haml.tt +10 -0
  94. data/lib/ditty/version.rb +1 -1
  95. data/lib/ditty.rb +6 -4
  96. data/lib/rubocop/cop/ditty/call_services_directly.rb +2 -2
  97. data/migrate/20181209_add_user_login_traits.rb +4 -4
  98. data/migrate/20190220_add_parent_id_to_roles.rb +9 -0
  99. data/spec/ditty/api_spec.rb +51 -0
  100. data/spec/ditty/controllers/roles_spec.rb +67 -0
  101. data/spec/ditty/controllers/user_login_traits_spec.rb +72 -0
  102. data/spec/ditty/controllers/users_spec.rb +72 -0
  103. data/spec/ditty/emails/base_spec.rb +76 -0
  104. data/spec/ditty/emails/forgot_password_spec.rb +20 -0
  105. data/spec/ditty/helpers/component_spec.rb +85 -0
  106. data/spec/ditty/models/user_spec.rb +36 -0
  107. data/spec/ditty/services/email_spec.rb +36 -0
  108. data/spec/ditty/services/logger_spec.rb +68 -0
  109. data/spec/ditty/services/settings_spec.rb +63 -0
  110. data/spec/ditty_spec.rb +9 -0
  111. data/spec/factories.rb +46 -0
  112. data/spec/fixtures/logger.yml +17 -0
  113. data/spec/fixtures/section.yml +3 -0
  114. data/spec/fixtures/settings.yml +8 -0
  115. data/spec/spec_helper.rb +51 -0
  116. data/spec/support/api_shared_examples.rb +250 -0
  117. data/spec/support/crud_shared_examples.rb +145 -0
  118. data/views/403.haml +1 -1
  119. data/views/404.haml +2 -4
  120. data/views/500.haml +11 -0
  121. data/views/audit_logs/index.haml +32 -33
  122. data/views/auth/forgot_password.haml +32 -16
  123. data/views/auth/identity.haml +14 -13
  124. data/views/auth/ldap.haml +2 -2
  125. data/views/auth/login.haml +23 -17
  126. data/views/auth/register.haml +20 -18
  127. data/views/auth/register_identity.haml +27 -12
  128. data/views/auth/reset_password.haml +36 -19
  129. data/views/blank.haml +43 -0
  130. data/views/embedded.haml +17 -11
  131. data/views/index.haml +1 -1
  132. data/views/layout.haml +45 -30
  133. data/views/partials/actions.haml +15 -14
  134. data/views/partials/content_tag.haml +0 -0
  135. data/views/partials/delete_form.haml +1 -1
  136. data/views/partials/filter_control.haml +2 -2
  137. data/views/partials/footer.haml +6 -5
  138. data/views/partials/form_control.haml +19 -12
  139. data/views/partials/form_tag.haml +1 -1
  140. data/views/partials/navitems.haml +42 -0
  141. data/views/partials/notifications.haml +12 -8
  142. data/views/partials/pager.haml +44 -25
  143. data/views/partials/search.haml +15 -11
  144. data/views/partials/sidebar.haml +15 -37
  145. data/views/partials/sort_ui.haml +2 -0
  146. data/views/partials/topbar.haml +53 -0
  147. data/views/partials/user_associations.haml +32 -0
  148. data/views/quick_start.haml +23 -0
  149. data/views/roles/display.haml +27 -6
  150. data/views/roles/edit.haml +3 -3
  151. data/views/roles/form.haml +1 -0
  152. data/views/roles/index.haml +23 -16
  153. data/views/roles/new.haml +2 -2
  154. data/views/user_login_traits/display.haml +4 -4
  155. data/views/user_login_traits/edit.haml +3 -3
  156. data/views/user_login_traits/index.haml +23 -25
  157. data/views/user_login_traits/new.haml +2 -2
  158. data/views/users/display.haml +14 -15
  159. data/views/users/edit.haml +3 -3
  160. data/views/users/form.haml +0 -0
  161. data/views/users/index.haml +31 -24
  162. data/views/users/login_traits.haml +6 -8
  163. data/views/users/new.haml +2 -2
  164. data/views/users/profile.haml +15 -15
  165. data/views/users/user.haml +1 -1
  166. metadata +271 -63
  167. data/lib/ditty/controllers/roles.rb +0 -13
  168. data/lib/ditty/controllers/user_login_traits.rb +0 -18
  169. data/views/partials/navbar.haml +0 -22
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ditty/controllers/application'
3
+ require 'ditty/controllers/application_controller'
4
4
  require 'ditty/models/role'
5
5
  require 'ditty/models/user'
6
+ require 'ditty/services/settings'
6
7
 
7
8
  module Ditty
8
- class Main < Application
9
+ class MainController < ApplicationController
9
10
  set track_actions: true
10
11
 
11
12
  before '/' do
@@ -17,6 +18,9 @@ module Ditty
17
18
 
18
19
  # Home Page
19
20
  get '/' do
21
+ home_page = Services::Settings['ditty.home_page']
22
+ redirect "#{settings.map_path}#{home_page}" if home_page
23
+
20
24
  authenticate!
21
25
  haml :index, locals: { title: 'Home' }
22
26
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/controllers/component_controller'
4
+ require 'ditty/models/role'
5
+ require 'ditty/policies/role_policy'
6
+
7
+ module Ditty
8
+ class RolesController < ::Ditty::ComponentController
9
+ SEARCHABLE = %i[name].freeze
10
+
11
+ set model_class: Role
12
+
13
+ helpers do
14
+ def parent_options(entity)
15
+ return policy_scope(::Ditty::Role) if entity.new?
16
+
17
+ policy_scope(::Ditty::Role)
18
+ .exclude(id: [entity.id] + entity.descendants.map(&:id))
19
+ .order(:name)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ditty/controllers/component_controller'
4
+ require 'ditty/models/user_login_trait'
5
+ require 'ditty/policies/user_login_trait_policy'
6
+
7
+ module Ditty
8
+ class UserLoginTraitsController < ::Ditty::ComponentController
9
+ SEARCHABLE = %i[platform device browser ip_address].freeze
10
+ FILTERS = [
11
+ { name: :user, field: 'user.email' },
12
+ { name: :platform },
13
+ { name: :device },
14
+ { name: :browser }
15
+ ].freeze
16
+
17
+ set base_path: '/login-traits'
18
+ set model_class: UserLoginTrait
19
+ set heading: 'Login'
20
+ # set track_actions: true
21
+
22
+ helpers do
23
+ def user_options
24
+ policy_scope(::Ditty::User).as_hash(:email, :email)
25
+ end
26
+
27
+ def platform_options
28
+ policy_scope(::Ditty::UserLoginTrait).select(:platform).distinct.as_hash(:platform, :platform)
29
+ end
30
+
31
+ def device_options
32
+ policy_scope(::Ditty::UserLoginTrait).select(:device).distinct.as_hash(:device, :device)
33
+ end
34
+
35
+ def browser_options
36
+ policy_scope(::Ditty::UserLoginTrait).select(:browser).distinct.as_hash(:browser, :browser)
37
+ end
38
+ end
39
+
40
+ def list
41
+ return super if params[:sort]
42
+
43
+ super.order(:updated_at).reverse
44
+ end
45
+ end
46
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ditty/controllers/component'
3
+ require 'ditty/controllers/component_controller'
4
4
  require 'ditty/models/user'
5
5
  require 'ditty/models/user_login_trait'
6
6
  require 'ditty/policies/user_policy'
@@ -8,7 +8,7 @@ require 'ditty/models/identity'
8
8
  require 'ditty/policies/identity_policy'
9
9
 
10
10
  module Ditty
11
- class Users < Ditty::Component
11
+ class UsersController < ::Ditty::ComponentController
12
12
  SEARCHABLE = %i[name surname email].freeze
13
13
 
14
14
  set model_class: User
@@ -28,8 +28,8 @@ module Ditty
28
28
 
29
29
  locals = { title: heading(:new) }
30
30
 
31
- user_params = permitted_attributes(User, :create)
32
- identity_params = permitted_attributes(Identity, :create)
31
+ user_params = permitted_parameters(User, :create)
32
+ identity_params = permitted_parameters(Identity, :create)
33
33
  user_params['email'] = identity_params['username']
34
34
  roles = user_params.delete('role_id')
35
35
 
@@ -38,7 +38,7 @@ module Ditty
38
38
 
39
39
  DB.transaction(isolation: :serializable) do
40
40
  begin
41
- identity.save
41
+ identity.save_changes
42
42
  rescue Sequel::ValidationFailed
43
43
  raise unless request.accept? 'text/html'
44
44
 
@@ -46,7 +46,7 @@ module Ditty
46
46
  locals = { title: heading(:new), entity: user, identity: identity }
47
47
  return haml(:"#{view_location}/new", locals: locals)
48
48
  end
49
- user.save
49
+ user.save_changes
50
50
  user.add_identity identity
51
51
 
52
52
  roles&.each do |role_id|
@@ -55,7 +55,7 @@ module Ditty
55
55
  user.check_roles
56
56
  end
57
57
 
58
- broadcast(:component_create, target: self)
58
+ broadcast(:component_create, target: self, entity: user)
59
59
  create_response(user)
60
60
  end
61
61
 
@@ -65,10 +65,10 @@ module Ditty
65
65
  halt 404 unless entity
66
66
  authorize entity, :update
67
67
 
68
- values = permitted_attributes(settings.model_class, :update)
68
+ values = permitted_parameters(settings.model_class, :update)
69
69
  roles = values.delete('role_id')
70
70
  entity.set values
71
- entity.save # Will trigger a Sequel::ValidationFailed exception if the model is incorrect
71
+ entity.save_changes # Will trigger a Sequel::ValidationFailed exception if the model is incorrect
72
72
 
73
73
  if roles
74
74
  entity.remove_all_roles
@@ -94,9 +94,10 @@ module Ditty
94
94
  return redirect back
95
95
  end
96
96
 
97
- values = permitted_attributes(Identity, :create)
97
+ values = permitted_parameters(Identity, :create)
98
98
  identity.set values
99
- if identity.valid? && identity.save
99
+ if identity.valid?
100
+ identity.save_changes
100
101
  broadcast(:identity_update_password, target: self)
101
102
  flash[:success] = 'Password Updated'
102
103
  redirect back
@@ -129,6 +130,7 @@ module Ditty
129
130
  halt 404 unless entity
130
131
  authorize entity, :read
131
132
 
133
+ flash[:redirect_to] = request.path
132
134
  haml :"#{view_location}/profile", locals: { entity: entity, identity: entity.identity.first, title: 'My Account' }
133
135
  end
134
136
  end
data/lib/ditty/db.rb CHANGED
@@ -8,7 +8,7 @@ require 'active_support/core_ext/object/blank'
8
8
  pool_timeout = (ENV['DB_POOL_TIMEOUT'] || 5).to_i
9
9
 
10
10
  if defined? DB
11
- Ditty::Services::Logger.instance.warn 'Database connection already set up'
11
+ ::Ditty::Services::Logger.warn 'Database connection already set up'
12
12
  elsif ENV['DATABASE_URL'].blank? == false
13
13
  # Delete DATABASE_URL from the environment, so it isn't accidently
14
14
  # passed to subprocesses. DATABASE_URL may contain passwords.
@@ -18,14 +18,16 @@ elsif ENV['DATABASE_URL'].blank? == false
18
18
  )
19
19
 
20
20
  DB.sql_log_level = (ENV['SEQUEL_LOGGING_LEVEL'] || :debug).to_sym
21
- DB.loggers << Ditty::Services::Logger.instance if ENV['DB_DEBUG'].to_i == 1
21
+ DB.loggers << ::Ditty::Services::Logger if ENV['DB_DEBUG'].to_i == 1
22
22
  DB.extension(:pagination)
23
23
  DB.extension(:schema_caching)
24
24
  DB.load_schema_cache?('./config/schema.dump')
25
25
 
26
- Sequel::Model.plugin :validation_helpers
27
- Sequel::Model.plugin :update_or_create
26
+ Sequel::Model.plugin :auto_validations
27
+ Sequel::Model.plugin :string_stripper
28
28
  Sequel::Model.plugin :timestamps, update_on_create: true
29
+ Sequel::Model.plugin :update_or_create
30
+ Sequel::Model.plugin :validation_helpers
29
31
  else
30
- Ditty::Services::Logger.instance.error 'No database connection set up'
32
+ ::Ditty::Services::Logger.error 'No database connection set up'
31
33
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'haml'
4
- require 'ditty/components/app'
4
+ require 'ditty/components/ditty'
5
5
 
6
6
  module Ditty
7
7
  module Emails
@@ -23,7 +23,10 @@ module Ditty
23
23
  @locals[param] ||= options[param]
24
24
  mail.send(param, options[param])
25
25
  end
26
- mail.body content
26
+ html = content
27
+ mail.html_part do
28
+ body html
29
+ end
27
30
  mail.deliver!
28
31
  end
29
32
 
@@ -34,50 +37,52 @@ module Ditty
34
37
  end
35
38
 
36
39
  def respond_to_missing?(method, _include_private = false)
37
- mail.respond_to? method
40
+ return true if mail.respond_to?(method)
41
+
42
+ super
38
43
  end
39
44
 
40
45
  private
41
46
 
42
- def content
43
- result = Haml::Engine.new(content_haml).render(Object.new, locals)
44
- return result unless options[:layout]
47
+ def content
48
+ result = Haml::Engine.new(content_haml).render(Object.new, locals)
49
+ return result unless options[:layout]
45
50
 
46
- Haml::Engine.new(layout_haml).render(Object.new, locals.merge(content: result))
47
- end
51
+ Haml::Engine.new(layout_haml).render(Object.new, locals.merge(content: result))
52
+ end
48
53
 
49
- def content_haml
50
- read_template(options[:view])
51
- end
54
+ def content_haml
55
+ read_template(options[:view])
56
+ end
52
57
 
53
- def layout_haml
54
- read_template("layouts/#{options[:layout]}") if options[:layout]
55
- end
58
+ def layout_haml
59
+ read_template("layouts/#{options[:layout]}") if options[:layout]
60
+ end
56
61
 
57
- def read_template(template)
58
- File.read(find_template("emails/#{template}"))
59
- end
62
+ def read_template(template)
63
+ File.read(find_template("emails/#{template}"))
64
+ end
60
65
 
61
- def base_options
62
- { subject: '(No Subject)', from: 'no-reply@ditty.io', view: :base, content_type: 'text/html; charset=UTF-8' }
63
- end
66
+ def base_options
67
+ { subject: '(No Subject)', from: 'no-reply@ditty.io', view: :base, content_type: 'text/html; charset=UTF-8' }
68
+ end
64
69
 
65
- def find_template(file)
66
- template = File.expand_path("./views/#{file}.haml")
67
- return template if File.file? template
70
+ def find_template(file)
71
+ template = File.expand_path("./views/#{file}.haml")
72
+ return template if File.file? template
68
73
 
69
- template = File.expand_path("./#{file}.haml", App.view_folder)
70
- return template if File.file? template
74
+ template = File.expand_path("./#{file}.haml", ::Ditty::Ditty.view_folder)
75
+ return template if File.file? template
71
76
 
72
- file
73
- end
77
+ file
78
+ end
74
79
 
75
- class << self
76
- def deliver!(to = nil, options = {})
77
- locals = options[:locals] || {}
78
- new(options).deliver!(to, locals)
80
+ class << self
81
+ def deliver!(to = nil, options = {})
82
+ locals = options[:locals] || {}
83
+ new(options).deliver!(to, locals)
84
+ end
79
85
  end
80
- end
81
86
  end
82
87
  end
83
88
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor/group'
4
+ require 'active_support/inflector'
5
+
6
+ module Ditty
7
+ module Generators
8
+ class CrudGenerator < Thor::Group
9
+ include Thor::Actions
10
+ attr_reader :namespace, :folder, :views_folder, :controller_name, :model_name, :policy_name # , :file_name
11
+
12
+ desc 'Create a CRUD Endpoint including the Model, Controller, Policy, Views and GraphQL Types'
13
+ argument :name, type: :string, desc: 'Name of the Model, eg. MyApp::User'
14
+
15
+ # --no-views make views optional
16
+ class_option :views, type: :boolean, default: true, desc: 'Generate views for controller'
17
+
18
+ def self.source_root
19
+ File.dirname(__FILE__)
20
+ end
21
+
22
+ def setup
23
+ @namespace = name.deconstantize
24
+ @folder = namespace.underscore
25
+ @model_name = name.demodulize
26
+ @views_folder = File.join('views', model_name.pluralize.underscore)
27
+ @controller_name = "#{model_name.pluralize}Controller"
28
+ @policy_name = "#{model_name}Policy"
29
+ end
30
+
31
+ def create_model
32
+ filename = File.join("lib/#{folder}/models", "#{model_name.underscore}.rb")
33
+ template '../templates/model.rb.erb', filename
34
+ rescue StandardError => e
35
+ puts "Could not generate model for #{model_name}: #{e.message}"
36
+ end
37
+
38
+ def create_controller
39
+ filename = File.join("lib/#{folder}/controllers", "#{controller_name.underscore}.rb")
40
+ template '../templates/controller.rb.erb', filename
41
+ # TODO: Insert the route into the component file
42
+ # insert_into_file 'config.ru', "use #{class_name}\n", after: 'run ApplicationController\n'
43
+ rescue StandardError => e
44
+ puts "Could not generate controller for #{model_name}: #{e.message}"
45
+ end
46
+
47
+ def create_policy
48
+ filename = File.join("lib/#{folder}/policies", "#{policy_name.underscore}.rb")
49
+ template '../templates/policy.rb.erb', filename
50
+ rescue StandardError => e
51
+ puts "Could not generate policy for #{model_name}: #{e.message}"
52
+ end
53
+
54
+ def create_type
55
+ filename = File.join("lib/#{folder}/types", "#{model_name.underscore}_type.rb")
56
+ template '../templates/type.rb.erb', filename
57
+ rescue StandardError => e
58
+ puts "Could not generate type for #{model_name}: #{e.message}"
59
+ end
60
+
61
+ def create_views
62
+ return unless options[:views]
63
+
64
+ directory '../templates/views', views_folder
65
+ end
66
+
67
+ private
68
+
69
+ def meta_columns
70
+ %i[id guid slug created_at updated_at]
71
+ end
72
+
73
+ def columns
74
+ require "#{folder}/models/#{model_name.underscore}"
75
+ name.constantize.columns
76
+ rescue StandardError
77
+ []
78
+ end
79
+
80
+ def schema
81
+ require "#{folder}/models/#{model_name.underscore}"
82
+ name.constantize.db_schema
83
+ rescue StandardError
84
+ []
85
+ end
86
+
87
+ def many_to_ones
88
+ DB.foreign_key_list(model_name.underscore.pluralize)
89
+ end
90
+
91
+ def name_column(table)
92
+ candidates = DB.schema(table.to_sym).to_h.keys - DB.foreign_key_list(table.to_sym).map do |e|
93
+ e[:columns]
94
+ end.flatten
95
+ (candidates - meta_columns).first
96
+ end
97
+
98
+ def graphql_types
99
+ @graphql_types ||= Hash.new('String').merge(
100
+ integer: 'Integer',
101
+ boolean: 'Boolean',
102
+ datetime: 'GraphQL::Types::ISO8601DateTime'
103
+ )
104
+ end
105
+
106
+ def input_types
107
+ @input_types ||= Hash.new('text').merge(
108
+ integer: 'number',
109
+ datetime: 'date'
110
+ )
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor/group'
4
+ require 'active_support/inflector'
5
+
6
+ module Ditty
7
+ module Generators
8
+ class MigrationGenerator < Thor::Group
9
+ include Thor::Actions
10
+
11
+ attr_reader :namespace, :folder
12
+
13
+ desc 'Creates a new Sequel migration for the current project'
14
+ argument :name, type: :string, desc: 'Name of the migration'
15
+
16
+ def self.source_root
17
+ File.expand_path('../templates', __dir__)
18
+ end
19
+
20
+ def create_model
21
+ filename = File.join('migrations', "#{Time.now.strftime('%Y%m%d')}_#{name.underscore}.rb")
22
+ template '../templates/migration.rb.erb', filename
23
+ end
24
+ end
25
+ end
26
+ end