motor-admin 0.1.12 → 0.1.13

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/motor/active_storage_attachments_controller.rb +28 -0
  3. data/app/controllers/motor/alerts_controller.rb +2 -0
  4. data/app/controllers/motor/configs_controller.rb +2 -0
  5. data/app/controllers/motor/dashboards_controller.rb +2 -0
  6. data/app/controllers/motor/data_controller.rb +3 -1
  7. data/app/controllers/motor/forms_controller.rb +2 -0
  8. data/app/controllers/motor/queries_controller.rb +2 -0
  9. data/app/controllers/motor/resources_controller.rb +2 -0
  10. data/app/controllers/motor/run_queries_controller.rb +2 -0
  11. data/app/controllers/motor/send_alerts_controller.rb +2 -0
  12. data/config/routes.rb +1 -0
  13. data/lib/motor.rb +1 -0
  14. data/lib/motor/active_record_utils.rb +2 -0
  15. data/lib/motor/active_record_utils/active_storage_links_extension.rb +15 -0
  16. data/lib/motor/active_record_utils/defined_scopes_extension.rb +19 -0
  17. data/lib/motor/admin.rb +15 -3
  18. data/lib/motor/api_query.rb +2 -0
  19. data/lib/motor/api_query/apply_scope.rb +26 -0
  20. data/lib/motor/build_schema.rb +60 -7
  21. data/lib/motor/build_schema/active_storage_attachment_schema.rb +84 -0
  22. data/lib/motor/build_schema/load_from_rails.rb +30 -56
  23. data/lib/motor/build_schema/merge_schema_configs.rb +13 -6
  24. data/lib/motor/build_schema/persist_resource_configs.rb +37 -1
  25. data/lib/motor/build_schema/reorder_schema.rb +3 -1
  26. data/lib/motor/version.rb +1 -1
  27. data/ui/dist/main-8f36a2746422efd1e8a8.css.gz +0 -0
  28. data/ui/dist/main-8f36a2746422efd1e8a8.js.gz +0 -0
  29. data/ui/dist/manifest.json +5 -5
  30. metadata +9 -4
  31. data/ui/dist/main-4bdedd7fcff1351efaf6.css.gz +0 -0
  32. data/ui/dist/main-4bdedd7fcff1351efaf6.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7905a9e6d289a069f10e5980bd861b2aec8da549e397bcd3b666c8fa8c75479
4
- data.tar.gz: 233b3747ee84e99c6c27ce72c9783c549d70a1ed12b568896bf8bef26d702ff5
3
+ metadata.gz: a9dca132c3a2481191219a518fe66e581988245cbe3212fb105c220285d1a5fa
4
+ data.tar.gz: e2cb07a305545d2fb01e0c94101f6bdabd705628b8043b8a8a40b320427bc7e9
5
5
  SHA512:
6
- metadata.gz: f62a5bfc817a1e339774277027a18e0c28f147fbd2c7078bfd013ec72a60b5aafebc28e065e064d0bf60cc63e18f48e7d7424ce3ba0c484d9489a107528cc7bf
7
- data.tar.gz: '01830208623fa1a67ea1c2e681a89b574c7c0ec62ff30ec2e3973918779e5c52913a81494ce0c9998dfd6d4a1cb3825cecab5c3ea1915d46b6a4eb922365ab9a'
6
+ metadata.gz: dcce9c0ad1fff7e751a91ab366174174421f038ee3ed848e32618d663d1955221295e0cc79904a0702b939630ab4e6b6873b2b141e01b6ac4bc8361233ef265f
7
+ data.tar.gz: b20916892df27d1a6c5eef23452824034d0559e5954fe44726034269496409358a3755f3b53dcd489d61953743b511e66f4f93c4bea2ea894f6dae856575492b
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ class ActiveStorageAttachmentsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
7
+ load_and_authorize_resource :attachment, class: 'ActiveStorage::Attachment', parent: false
8
+
9
+ def create
10
+ if @attachment.record.respond_to?("#{@attachment.name}_attachment=") || @attachment.record.respond_to?("#{@attachment.name}_attachments=")
11
+ @attachment.record.public_send(@attachment.name).attach(
12
+ io: StringIO.new(params.dig(:data, :file, :io).to_s.encode('ISO-8859-1')),
13
+ filename: params.dig(:data, :file, :filename)
14
+ )
15
+
16
+ head :ok
17
+ else
18
+ head :unprocessable_entity
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def attachment_params
25
+ params.require(:data).except(:file).permit!
26
+ end
27
+ end
28
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class AlertsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :alert, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_alert, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class ConfigsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource
6
8
 
7
9
  def index
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class DashboardsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :dashboard, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_dashboard, only: :create
@@ -4,6 +4,8 @@ module Motor
4
4
  class DataController < ApiBaseController
5
5
  INSTANCE_VARIABLE_NAME = 'resource'
6
6
 
7
+ wrap_parameters :data, except: %i[include fields]
8
+
7
9
  before_action :load_and_authorize_resource
8
10
  before_action :load_and_authorize_association
9
11
 
@@ -96,7 +98,7 @@ module Motor
96
98
  end
97
99
 
98
100
  def resource_params
99
- params.fetch(:data, {}).except(resource_class.primary_key).permit!
101
+ params.require(:data).except(resource_class.primary_key).permit!
100
102
  end
101
103
  end
102
104
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class FormsController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :form, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_form, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class QueriesController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource :query, only: %i[index show update destroy]
6
8
 
7
9
  before_action :build_query, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class ResourcesController < ApiBaseController
5
+ wrap_parameters :data, except: %i[include fields]
6
+
5
7
  load_and_authorize_resource
6
8
 
7
9
  def index
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class RunQueriesController < ApiBaseController
5
+ wrap_parameters :data
6
+
5
7
  load_and_authorize_resource :query, only: :show, parent: false
6
8
 
7
9
  before_action :build_query, only: :create
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Motor
4
4
  class SendAlertsController < ApiBaseController
5
+ wrap_parameters :data
6
+
5
7
  before_action :build_alert, only: :create
6
8
  authorize_resource :alert, only: :create
7
9
 
data/config/routes.rb CHANGED
@@ -13,6 +13,7 @@ Motor::Admin.routes.draw do
13
13
  resources :dashboards, only: %i[index show create update destroy]
14
14
  resources :forms, only: %i[index show create update destroy]
15
15
  resources :alerts, only: %i[index show create update destroy]
16
+ resources :active_storage_attachments, only: %i[create], path: 'data/active_storage__attachments'
16
17
  resource :schema, only: %i[show update]
17
18
  resources :resources, path: '/data/:resource',
18
19
  only: %i[index show update create destroy],
data/lib/motor.rb CHANGED
@@ -6,6 +6,7 @@ require 'js_regex'
6
6
  require 'fugit'
7
7
  require 'csv'
8
8
  require 'active_record/filter'
9
+ require 'base64'
9
10
 
10
11
  module Motor
11
12
  PATH = Pathname.new(__dir__)
@@ -2,6 +2,8 @@
2
2
 
3
3
  require_relative './active_record_utils/types'
4
4
  require_relative './active_record_utils/fetch_methods'
5
+ require_relative './active_record_utils/defined_scopes_extension'
6
+ require_relative './active_record_utils/active_storage_links_extension'
5
7
 
6
8
  module ActiveRecordUtils
7
9
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module ActiveStorageLinksExtension
6
+ def path
7
+ Rails.application.routes.url_helpers.rails_blob_path(self)
8
+ end
9
+
10
+ def url
11
+ Rails.application.routes.url_helpers.url_for(self)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module DefinedScopesExtension
6
+ def scope(name, _body)
7
+ (@__scopes__ ||= []) << name
8
+
9
+ super
10
+ end
11
+
12
+ def defined_scopes
13
+ @__scopes__ || []
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Base.extend(Motor::ActiveRecordUtils::DefinedScopesExtension)
data/lib/motor/admin.rb CHANGED
@@ -2,11 +2,23 @@
2
2
 
3
3
  module Motor
4
4
  class Admin < ::Rails::Engine
5
+ initializer 'motor.filter_params' do
6
+ Rails.application.config.filter_parameters += %i[io]
7
+ end
8
+
5
9
  initializer 'motor.alerts.scheduler' do
6
- next if defined?(Sidekiq) && Sidekiq.server?
10
+ config.after_initialize do |_app|
11
+ next if defined?(Sidekiq) && Sidekiq.server?
12
+
13
+ Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
14
+ Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
15
+ end
16
+ end
7
17
 
8
- Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
9
- Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
18
+ initializer 'motor.active_storage.extensions' do
19
+ ActiveSupport.on_load(:active_storage_attachment) do
20
+ ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
21
+ end
10
22
  end
11
23
  end
12
24
  end
@@ -4,6 +4,7 @@ require_relative './api_query/sort'
4
4
  require_relative './api_query/paginate'
5
5
  require_relative './api_query/filter'
6
6
  require_relative './api_query/search'
7
+ require_relative './api_query/apply_scope'
7
8
  require_relative './api_query/build_meta'
8
9
  require_relative './api_query/build_json'
9
10
 
@@ -15,6 +16,7 @@ module Motor
15
16
  rel = ApiQuery::Sort.call(rel, params[:sort])
16
17
  rel = ApiQuery::Paginate.call(rel, params[:page])
17
18
  rel = ApiQuery::Filter.call(rel, params[:filter])
19
+ rel = ApiQuery::ApplyScope.call(rel, params[:scope])
18
20
 
19
21
  ApiQuery::Search.call(rel, params[:q] || params[:search] || params[:query])
20
22
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module ApiQuery
5
+ module ApplyScope
6
+ module_function
7
+
8
+ def call(rel, scope)
9
+ return rel if scope.blank?
10
+
11
+ scope_symbol = scope.to_sym
12
+
13
+ if rel.klass.defined_scopes.include?(scope_symbol)
14
+ rel.public_send(scope_symbol)
15
+ else
16
+ configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
17
+ scope_configs = configs.preferences[:scopes].find { |s| s[:name] == scope }
18
+
19
+ return rel unless scope_configs
20
+
21
+ ApiQuery::Filter.call(rel, scope_configs[:preferences][:filter])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,16 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './build_schema/load_from_rails'
4
- require_relative './build_schema/find_display_column'
5
- require_relative './build_schema/persist_resource_configs'
6
- require_relative './build_schema/reorder_schema'
7
- require_relative './build_schema/merge_schema_configs'
8
- require_relative './build_schema/utils'
9
-
10
3
  module Motor
11
4
  module BuildSchema
5
+ module ColumnAccessTypes
6
+ ALL = [
7
+ READ_ONLY = 'read_only',
8
+ WRITE_ONLY = 'write_only',
9
+ READ_WRITE = 'read_write',
10
+ HIDDEN = 'hidden'
11
+ ].freeze
12
+ end
13
+
12
14
  SEARCHABLE_COLUMN_TYPES = %i[citext text string bitstring].freeze
13
15
 
16
+ COLUMN_NAME_ACCESS_TYPES = {
17
+ id: ColumnAccessTypes::READ_ONLY,
18
+ created_at: ColumnAccessTypes::READ_ONLY,
19
+ updated_at: ColumnAccessTypes::READ_ONLY,
20
+ deleted_at: ColumnAccessTypes::READ_ONLY
21
+ }.with_indifferent_access.freeze
22
+
23
+ DEFAULT_SCOPE_TYPE = 'default'
24
+
25
+ DEFAULT_ACTIONS = [
26
+ {
27
+ name: 'create',
28
+ display_name: 'Create',
29
+ action_type: 'default',
30
+ preferences: {},
31
+ visible: true
32
+ },
33
+ {
34
+ name: 'edit',
35
+ display_name: 'Edit',
36
+ action_type: 'default',
37
+ preferences: {},
38
+ visible: true
39
+ },
40
+ {
41
+ name: 'remove',
42
+ display_name: 'Remove',
43
+ action_type: 'default',
44
+ preferences: {},
45
+ visible: true
46
+ }
47
+ ].freeze
48
+
49
+ DEFAULT_TABS = [
50
+ {
51
+ name: 'summary',
52
+ display_name: 'Summary',
53
+ tab_type: 'default',
54
+ preferences: {},
55
+ visible: true
56
+ }
57
+ ].freeze
58
+
14
59
  module_function
15
60
 
16
61
  def call
@@ -21,3 +66,11 @@ module Motor
21
66
  end
22
67
  end
23
68
  end
69
+
70
+ require_relative './build_schema/active_storage_attachment_schema'
71
+ require_relative './build_schema/load_from_rails'
72
+ require_relative './build_schema/find_display_column'
73
+ require_relative './build_schema/persist_resource_configs'
74
+ require_relative './build_schema/reorder_schema'
75
+ require_relative './build_schema/merge_schema_configs'
76
+ require_relative './build_schema/utils'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module BuildSchema
5
+ ACTIVE_STORAGE_ATTACHMENT_SCHEMA = {
6
+ name: 'active_storage/attachment',
7
+ slug: 'active_storage__attachments',
8
+ table_name: 'active_storage_attachments',
9
+ primary_key: 'id',
10
+ display_name: 'Attachments',
11
+ display_column: 'filename',
12
+ columns: [
13
+ {
14
+ name: 'id',
15
+ display_name: 'Id',
16
+ column_type: 'integer',
17
+ access_type: 'read_only',
18
+ default_value: nil,
19
+ validators: [],
20
+ virtual: false
21
+ },
22
+ {
23
+ name: 'path',
24
+ display_name: 'Path',
25
+ column_type: 'string',
26
+ access_type: 'read_only',
27
+ default_value: nil,
28
+ validators: [],
29
+ virtual: false
30
+ },
31
+ {
32
+ name: 'name',
33
+ display_name: 'Name',
34
+ column_type: 'string',
35
+ access_type: 'read_write',
36
+ default_value: nil,
37
+ validators: [],
38
+ virtual: false
39
+ },
40
+ {
41
+ name: 'record_type',
42
+ display_name: 'Record type',
43
+ column_type: 'string',
44
+ access_type: 'read_write',
45
+ default_value: nil,
46
+ validators: [],
47
+ virtual: false
48
+ },
49
+ {
50
+ name: 'record_id',
51
+ display_name: 'Record',
52
+ column_type: 'integer',
53
+ access_type: 'read_write',
54
+ default_value: nil,
55
+ validators: [],
56
+ virtual: false
57
+ },
58
+ {
59
+ name: 'file',
60
+ display_name: 'File',
61
+ column_type: 'file',
62
+ access_type: 'write_only',
63
+ default_value: nil,
64
+ validators: [],
65
+ virtual: false
66
+ },
67
+ {
68
+ name: 'created_at',
69
+ display_name: 'Created at',
70
+ column_type: 'datetime',
71
+ access_type: 'read_only',
72
+ default_value: nil,
73
+ validators: [],
74
+ virtual: false
75
+ }
76
+ ],
77
+ associations: [],
78
+ scopes: [],
79
+ actions: Motor::BuildSchema::DEFAULT_ACTIONS.reject { |e| e[:name] == 'edit' },
80
+ tabs: Motor::BuildSchema::DEFAULT_TABS,
81
+ visible: true
82
+ }.with_indifferent_access
83
+ end
84
+ end
@@ -3,56 +3,6 @@
3
3
  module Motor
4
4
  module BuildSchema
5
5
  module LoadFromRails
6
- module ColumnAccessTypes
7
- ALL = [
8
- READ_ONLY = 'read_only',
9
- WRITE_ONLY = 'write_only',
10
- READ_WRITE = 'read_write',
11
- HIDDEN = 'hidden'
12
- ].freeze
13
- end
14
-
15
- COLUMN_NAME_ACCESS_TYPES = {
16
- id: ColumnAccessTypes::READ_ONLY,
17
- created_at: ColumnAccessTypes::READ_ONLY,
18
- updated_at: ColumnAccessTypes::READ_ONLY,
19
- deleted_at: ColumnAccessTypes::READ_ONLY
20
- }.with_indifferent_access.freeze
21
-
22
- DEFAULT_ACTIONS = [
23
- {
24
- name: 'create',
25
- display_name: 'Create',
26
- action_type: 'default',
27
- preferences: {},
28
- visible: true
29
- },
30
- {
31
- name: 'edit',
32
- display_name: 'Edit',
33
- action_type: 'default',
34
- preferences: {},
35
- visible: true
36
- },
37
- {
38
- name: 'remove',
39
- display_name: 'Remove',
40
- action_type: 'default',
41
- preferences: {},
42
- visible: true
43
- }
44
- ].freeze
45
-
46
- DEFAULT_TABS = [
47
- {
48
- name: 'summary',
49
- display_name: 'Summary',
50
- tab_type: 'default',
51
- preferences: {},
52
- visible: true
53
- }
54
- ].freeze
55
-
56
6
  module_function
57
7
 
58
8
  def call
@@ -86,21 +36,43 @@ module Motor
86
36
  end
87
37
 
88
38
  def build_model_schema(model)
39
+ model_name = model.name
40
+
41
+ return Motor::BuildSchema::ACTIVE_STORAGE_ATTACHMENT_SCHEMA if model_name == 'ActiveStorage::Attachment'
42
+
89
43
  {
90
- name: model.name.underscore,
44
+ name: model_name.underscore,
91
45
  slug: Utils.slugify(model),
92
46
  table_name: model.table_name,
47
+ class_name: model.name,
93
48
  primary_key: model.primary_key,
94
- display_name: model.name.titleize.pluralize,
49
+ display_name: model_name.titleize.pluralize,
95
50
  display_column: FindDisplayColumn.call(model),
96
51
  columns: fetch_columns(model),
97
52
  associations: fetch_associations(model),
53
+ scopes: fetch_scopes(model),
98
54
  actions: DEFAULT_ACTIONS,
99
55
  tabs: DEFAULT_TABS,
100
56
  visible: true
101
57
  }.with_indifferent_access
102
58
  end
103
59
 
60
+ def fetch_scopes(model)
61
+ model.defined_scopes.map do |scope_name|
62
+ scope_name = scope_name.to_s
63
+
64
+ next if scope_name.starts_with?('with_attached')
65
+
66
+ {
67
+ name: scope_name,
68
+ display_name: scope_name.humanize,
69
+ scope_type: DEFAULT_SCOPE_TYPE,
70
+ visible: true,
71
+ preferences: {}
72
+ }
73
+ end.compact
74
+ end
75
+
104
76
  def fetch_columns(model)
105
77
  default_attrs = model.new.attributes
106
78
 
@@ -127,17 +99,19 @@ module Motor
127
99
  next
128
100
  end
129
101
 
130
- next if defined?(ActiveStorage::Blob) && ref.klass == ActiveStorage::Blob
102
+ model_class = ref.klass
103
+
104
+ next if model_class.name == 'ActiveStorage::Blob'
131
105
 
132
106
  {
133
107
  name: name,
134
108
  display_name: name.humanize,
135
109
  slug: name.underscore,
136
- model_name: ref.klass.name.underscore,
137
- model_slug: Utils.slugify(ref.klass),
110
+ model_name: model_class.name.underscore,
111
+ model_slug: Utils.slugify(model_class),
138
112
  association_type: fetch_association_type(ref),
139
113
  foreign_key: ref.foreign_key,
140
- polymorphic: ref.polymorphic?,
114
+ polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
141
115
  visible: true
142
116
  }
143
117
  end.compact
@@ -7,6 +7,7 @@ module Motor
7
7
  COLUMN_DEFAULTS = PersistResourceConfigs::COLUMN_DEFAULTS
8
8
  ACTION_DEFAULTS = PersistResourceConfigs::ACTION_DEFAULTS
9
9
  TAB_DEFAULTS = PersistResourceConfigs::TAB_DEFAULTS
10
+ SCOPE_DEFAULTS = PersistResourceConfigs::SCOPE_DEFAULTS
10
11
 
11
12
  module_function
12
13
 
@@ -26,17 +27,17 @@ module Motor
26
27
  def merge_model(model, configs)
27
28
  updated_model = model.merge(configs.slice(*RESOURCE_ATTRS))
28
29
 
30
+ updated_model[:associations] = merge_by_name(
31
+ model[:associations],
32
+ configs[:associations]
33
+ )
34
+
29
35
  updated_model[:columns] = merge_by_name(
30
36
  model[:columns],
31
37
  configs[:columns],
32
38
  COLUMN_DEFAULTS
33
39
  )
34
40
 
35
- updated_model[:associations] = merge_by_name(
36
- model[:associations],
37
- configs[:associations]
38
- )
39
-
40
41
  updated_model[:actions] = merge_by_name(
41
42
  model[:actions],
42
43
  configs[:actions],
@@ -46,7 +47,13 @@ module Motor
46
47
  updated_model[:tabs] = merge_by_name(
47
48
  model[:tabs],
48
49
  configs[:tabs],
49
- ACTION_DEFAULTS
50
+ TAB_DEFAULTS
51
+ )
52
+
53
+ updated_model[:scopes] = merge_by_name(
54
+ model[:scopes],
55
+ configs[:scopes],
56
+ SCOPE_DEFAULTS
50
57
  )
51
58
 
52
59
  updated_model
@@ -6,6 +6,7 @@ module Motor
6
6
  RESOURCE_ATTRS = %w[display_name visible].freeze
7
7
  COLUMN_ATTRS = %w[name display_name column_type access_type default_value virtual].freeze
8
8
  ASSOCIATION_ATTRS = %w[name display_name visible].freeze
9
+ SCOPE_ATTRS = %w[name display_name scope_type preferences visible].freeze
9
10
  ACTION_ATTRS = %w[name display_name action_type preferences visible].freeze
10
11
  TAB_ATTRS = %w[name display_name tab_type preferences visible].freeze
11
12
 
@@ -25,6 +26,12 @@ module Motor
25
26
  preferences: {}
26
27
  }.with_indifferent_access
27
28
 
29
+ SCOPE_DEFAULTS = {
30
+ visible: true,
31
+ scope_type: 'default',
32
+ preferences: {}
33
+ }.with_indifferent_access
34
+
28
35
  module_function
29
36
 
30
37
  # @param resource [Motor::Resource]
@@ -99,6 +106,14 @@ module Motor
99
106
  )
100
107
  end
101
108
 
109
+ if new_prefs[:scopes].present?
110
+ normalized_preferences[:scopes] = normalize_scopes(
111
+ default_prefs[:scopes],
112
+ existing_prefs.fetch(:scopes, []),
113
+ new_prefs.fetch(:scopes, [])
114
+ )
115
+ end
116
+
102
117
  normalized_preferences.compact
103
118
  end
104
119
 
@@ -138,7 +153,7 @@ module Motor
138
153
  action_attrs = new_action.slice(*ACTION_ATTRS)
139
154
 
140
155
  normalized_action = existing_action.merge(action_attrs)
141
- normalized_action = reject_default(default_action.presence || TAB_DEFAULTS, normalized_action)
156
+ normalized_action = reject_default(default_action.presence || ACTION_DEFAULTS, normalized_action)
142
157
 
143
158
  normalized_action.merge(name: name) if normalized_action.present?
144
159
  end.compact.presence
@@ -165,6 +180,27 @@ module Motor
165
180
  end.compact.presence
166
181
  end
167
182
 
183
+ # @param default_scopes [Array<HashWithIndifferentAccess>]
184
+ # @param existing_scopes [Array<HashWithIndifferentAccess>]
185
+ # @param new_scopes [Array<HashWithIndifferentAccess>]
186
+ # @return [Array<HashWithIndifferentAccess>]
187
+ def normalize_scopes(default_scopes, existing_scopes, new_scopes)
188
+ (existing_scopes.pluck(:name) + new_scopes.pluck(:name)).uniq.map do |name|
189
+ new_scope = safe_fetch_by_name(new_scopes, name)
190
+
191
+ next if new_scope[:_remove]
192
+
193
+ existing_scope = safe_fetch_by_name(existing_scopes, name)
194
+ default_scope = safe_fetch_by_name(default_scopes, name)
195
+ scope_attrs = new_scope.slice(*SCOPE_ATTRS)
196
+
197
+ normalized_scope = existing_scope.merge(scope_attrs)
198
+ normalized_scope = reject_default(default_scope.presence || SCOPE_DEFAULTS, normalized_scope)
199
+
200
+ normalized_scope.merge(name: name) if normalized_scope.present?
201
+ end.compact.presence
202
+ end
203
+
168
204
  # @param default_assocs [Array<HashWithIndifferentAccess>]
169
205
  # @param existing_assocs [Array<HashWithIndifferentAccess>]
170
206
  # @param new_assocs [Array<HashWithIndifferentAccess>]
@@ -16,12 +16,14 @@ module Motor
16
16
  associations_order = configs["resources.#{model[:name]}.associations.order"]
17
17
  actions_order = configs["resources.#{model[:name]}.actions.order"]
18
18
  tabs_order = configs["resources.#{model[:name]}.tabs.order"]
19
+ scopes_order = configs["resources.#{model[:name]}.scopes.order"]
19
20
 
20
21
  model.merge(
21
22
  columns: sort_by_name(model[:columns], columns_order, sort_alphabetically: false),
22
23
  associations: sort_by_name(model[:associations], associations_order),
23
24
  actions: sort_by_name(model[:actions], actions_order, sort_alphabetically: false),
24
- tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false)
25
+ tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false),
26
+ scopes: sort_by_name(model[:scopes], scopes_order)
25
27
  )
26
28
  end
27
29
  end
data/lib/motor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motor
4
- VERSION = '0.1.12'
4
+ VERSION = '0.1.13'
5
5
  end
@@ -5,9 +5,9 @@
5
5
  "fonts/ionicons.ttf?v=3.0.0-alpha.3": "fonts/ionicons.ttf",
6
6
  "fonts/ionicons.woff2?v=3.0.0-alpha.3": "fonts/ionicons.woff2",
7
7
  "fonts/ionicons.woff?v=3.0.0-alpha.3": "fonts/ionicons.woff",
8
- "main-4bdedd7fcff1351efaf6.css.gz": "main-4bdedd7fcff1351efaf6.css.gz",
9
- "main-4bdedd7fcff1351efaf6.js.LICENSE.txt": "main-4bdedd7fcff1351efaf6.js.LICENSE.txt",
10
- "main-4bdedd7fcff1351efaf6.js.gz": "main-4bdedd7fcff1351efaf6.js.gz",
11
- "main.css": "main-4bdedd7fcff1351efaf6.css",
12
- "main.js": "main-4bdedd7fcff1351efaf6.js"
8
+ "main-8f36a2746422efd1e8a8.css.gz": "main-8f36a2746422efd1e8a8.css.gz",
9
+ "main-8f36a2746422efd1e8a8.js.LICENSE.txt": "main-8f36a2746422efd1e8a8.js.LICENSE.txt",
10
+ "main-8f36a2746422efd1e8a8.js.gz": "main-8f36a2746422efd1e8a8.js.gz",
11
+ "main.css": "main-8f36a2746422efd1e8a8.css",
12
+ "main.js": "main-8f36a2746422efd1e8a8.js"
13
13
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motor-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Matsyburka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-27 00:00:00.000000000 Z
11
+ date: 2021-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -132,6 +132,7 @@ files:
132
132
  - LICENSE
133
133
  - README.md
134
134
  - Rakefile
135
+ - app/controllers/motor/active_storage_attachments_controller.rb
135
136
  - app/controllers/motor/alerts_controller.rb
136
137
  - app/controllers/motor/api_base_controller.rb
137
138
  - app/controllers/motor/application_controller.rb
@@ -172,6 +173,8 @@ files:
172
173
  - lib/motor-admin.rb
173
174
  - lib/motor.rb
174
175
  - lib/motor/active_record_utils.rb
176
+ - lib/motor/active_record_utils/active_storage_links_extension.rb
177
+ - lib/motor/active_record_utils/defined_scopes_extension.rb
175
178
  - lib/motor/active_record_utils/fetch_methods.rb
176
179
  - lib/motor/active_record_utils/types.rb
177
180
  - lib/motor/admin.rb
@@ -181,6 +184,7 @@ files:
181
184
  - lib/motor/alerts/scheduler.rb
182
185
  - lib/motor/api.rb
183
186
  - lib/motor/api_query.rb
187
+ - lib/motor/api_query/apply_scope.rb
184
188
  - lib/motor/api_query/build_json.rb
185
189
  - lib/motor/api_query/build_meta.rb
186
190
  - lib/motor/api_query/filter.rb
@@ -189,6 +193,7 @@ files:
189
193
  - lib/motor/api_query/sort.rb
190
194
  - lib/motor/assets.rb
191
195
  - lib/motor/build_schema.rb
196
+ - lib/motor/build_schema/active_storage_attachment_schema.rb
192
197
  - lib/motor/build_schema/find_display_column.rb
193
198
  - lib/motor/build_schema/load_from_rails.rb
194
199
  - lib/motor/build_schema/merge_schema_configs.rb
@@ -208,8 +213,8 @@ files:
208
213
  - lib/motor/ui_configs.rb
209
214
  - lib/motor/version.rb
210
215
  - ui/dist/fonts/ionicons.woff2
211
- - ui/dist/main-4bdedd7fcff1351efaf6.css.gz
212
- - ui/dist/main-4bdedd7fcff1351efaf6.js.gz
216
+ - ui/dist/main-8f36a2746422efd1e8a8.css.gz
217
+ - ui/dist/main-8f36a2746422efd1e8a8.js.gz
213
218
  - ui/dist/manifest.json
214
219
  homepage:
215
220
  licenses: