motor-admin 0.1.13 → 0.1.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9dca132c3a2481191219a518fe66e581988245cbe3212fb105c220285d1a5fa
4
- data.tar.gz: e2cb07a305545d2fb01e0c94101f6bdabd705628b8043b8a8a40b320427bc7e9
3
+ metadata.gz: 690872e084d43d0b2759f48a2d33b7399470b6dec88678248982daf0b0ca2e8a
4
+ data.tar.gz: 3949cd11e7773c19dd03c77dd1b52477ed282e3e40e9e688bf9018e7c93c2b13
5
5
  SHA512:
6
- metadata.gz: dcce9c0ad1fff7e751a91ab366174174421f038ee3ed848e32618d663d1955221295e0cc79904a0702b939630ab4e6b6873b2b141e01b6ac4bc8361233ef265f
7
- data.tar.gz: b20916892df27d1a6c5eef23452824034d0559e5954fe44726034269496409358a3755f3b53dcd489d61953743b511e66f4f93c4bea2ea894f6dae856575492b
6
+ metadata.gz: 244c24b44ab251e6a71fe9e4200062b8d6c3679effb03c902759f3ef389d87cf87f8cf7fcf5c48c15a0c4358fc0ee750964b03c17eccd2f3af7cba19a0282ba8
7
+ data.tar.gz: 9225e1067d918fccd2dbef1c661e5128299579bd05fe4b52738bcb1ca2cc90c7495cbd07a556661000654ff0e4f6fd3e76d8049d44e3748f759e39566f89ffeb
@@ -8,6 +8,7 @@ module Motor
8
8
 
9
9
  before_action :load_and_authorize_resource
10
10
  before_action :load_and_authorize_association
11
+ before_action :wrap_io_params
11
12
 
12
13
  def index
13
14
  @resources = Motor::ApiQuery.call(@resources, params)
@@ -98,7 +99,23 @@ module Motor
98
99
  end
99
100
 
100
101
  def resource_params
101
- params.require(:data).except(resource_class.primary_key).permit!
102
+ if params[:data].present?
103
+ params.require(:data).except(resource_class.primary_key).permit!
104
+ else
105
+ {}
106
+ end
107
+ end
108
+
109
+ def wrap_io_params(hash = params)
110
+ hash.each do |key, value|
111
+ if key == 'io'
112
+ hash[key] = StringIO.new(value.encode('ISO-8859-1'))
113
+ elsif value.is_a?(ActionController::Parameters)
114
+ wrap_io_params(value)
115
+ end
116
+ end
117
+
118
+ hash
102
119
  end
103
120
  end
104
121
  end
data/lib/motor/admin.rb CHANGED
@@ -8,10 +8,9 @@ module Motor
8
8
 
9
9
  initializer 'motor.alerts.scheduler' do
10
10
  config.after_initialize do |_app|
11
- next if defined?(Sidekiq) && Sidekiq.server?
11
+ next unless defined?(Rails::Server)
12
12
 
13
13
  Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
14
- Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
15
14
  end
16
15
  end
17
16
 
@@ -3,10 +3,6 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module ScheduledAlertsCache
6
- UPDATE_ALERTS_TASK = Concurrent::TimerTask.new(
7
- execution_interval: 2.minutes
8
- ) { Motor::Alerts::ScheduledAlertsCache.load_alerts }
9
-
10
6
  CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
11
7
 
12
8
  module_function
@@ -49,7 +49,8 @@ module Motor
49
49
 
50
50
  params[:fields].each do |key, fields|
51
51
  fields = fields.split(',') if fields.is_a?(String)
52
- fields_hash = build_fields_hash(model, fields)
52
+ reflection_class = model.reflections[key]&.klass
53
+ fields_hash = build_fields_hash(reflection_class || model, fields)
53
54
 
54
55
  if key == model_name || model_name.split('/').last == key
55
56
  json_params.merge!(fields_hash)
@@ -68,7 +69,7 @@ module Motor
68
69
  fields.each_with_object(fields_hash) do |field, acc|
69
70
  if field.in?(columns)
70
71
  acc['only'] << field
71
- else
72
+ elsif model.instance_methods.include?(field.to_sym)
72
73
  acc['methods'] << field
73
74
  end
74
75
  end
@@ -7,19 +7,39 @@ module Motor
7
7
 
8
8
  module_function
9
9
 
10
- def call(rel, params)
11
- return rel if params.blank?
10
+ def call(rel, param)
11
+ return rel if param.blank?
12
12
 
13
- normalized_params = build_params(params)
13
+ arel_order = build_arel_order(rel.klass, param)
14
+ join_params = build_join_params(rel.klass, param)
14
15
 
15
- rel.order(normalized_params)
16
+ rel.order(arel_order).left_joins(join_params)
16
17
  end
17
18
 
18
- def build_params(param)
19
- param.split(',').each_with_object({}) do |field, hash|
20
- direction, name = field.match(FIELD_PARSE_REGEXP).captures
19
+ def build_join_params(_model, param)
20
+ param.split(',').each_with_object({}) do |field, result|
21
+ key = field[FIELD_PARSE_REGEXP, 2]
22
+ *path, _ = key.split('.')
21
23
 
22
- hash[name] = direction.present? ? :desc : :asc
24
+ path.reduce(result) do |acc, fragment|
25
+ acc[fragment] = {}
26
+ end
27
+ end
28
+ end
29
+
30
+ def build_arel_order(model, param)
31
+ param.split(',').map do |field|
32
+ direction, key = field.match(FIELD_PARSE_REGEXP).captures
33
+ *path, field = key.split('.')
34
+
35
+ reflection_model =
36
+ path.reduce(model) do |acc, fragment|
37
+ acc.reflections[fragment].klass
38
+ end
39
+
40
+ arel_column = reflection_model.arel_table[field]
41
+
42
+ direction.present? ? arel_column.desc : arel_column.asc
23
43
  end
24
44
  end
25
45
  end
@@ -26,7 +26,7 @@ module Motor
26
26
  access_type: 'read_only',
27
27
  default_value: nil,
28
28
  validators: [],
29
- virtual: false
29
+ virtual: true
30
30
  },
31
31
  {
32
32
  name: 'name',
@@ -76,22 +76,80 @@ module Motor
76
76
  def fetch_columns(model)
77
77
  default_attrs = model.new.attributes
78
78
 
79
- model.columns.map do |column|
79
+ reference_columns = fetch_reference_columns(model)
80
+
81
+ table_columns =
82
+ model.columns.map do |column|
83
+ next if reference_columns.find { |c| c[:name] == column.name }
84
+
85
+ {
86
+ name: column.name,
87
+ display_name: column.name.humanize,
88
+ column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
89
+ access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
90
+ default_value: default_attrs[column.name],
91
+ validators: fetch_validators(model, column.name),
92
+ reference: nil,
93
+ virtual: false
94
+ }
95
+ end.compact
96
+
97
+ reference_columns + table_columns
98
+ end
99
+
100
+ def build_column(model, column)
101
+ {
102
+ name: column.name,
103
+ display_name: column.name.humanize,
104
+ column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
105
+ access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
106
+ default_value: default_attrs[column.name],
107
+ validators: fetch_validators(model, column.name),
108
+ reference: fetch_reference(model, column.name),
109
+ virtual: false
110
+ }
111
+ end
112
+
113
+ def fetch_reference_columns(model)
114
+ default_attrs = model.new.attributes
115
+
116
+ model.reflections.map do |name, ref|
117
+ next if !ref.has_one? && !ref.belongs_to?
118
+
119
+ begin
120
+ ref.klass
121
+ rescue StandardError
122
+ next
123
+ end
124
+
125
+ column_name = ref.belongs_to? ? ref.foreign_key : name
126
+
127
+ next if ref.klass.name == 'ActiveStorage::Blob'
128
+
129
+ is_attachment = ref.klass.name == 'ActiveStorage::Attachment'
130
+
80
131
  {
81
- name: column.name,
82
- display_name: column.name.humanize,
83
- column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
84
- access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
85
- default_value: default_attrs[column.name],
86
- validators: fetch_validators(model, column.name),
132
+ name: column_name,
133
+ display_name: column_name.humanize,
134
+ column_type: is_attachment ? 'file' : 'integer',
135
+ access_type: ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY,
136
+ default_value: default_attrs[column_name],
137
+ validators: fetch_validators(model, column_name),
138
+ reference: {
139
+ name: name,
140
+ model_name: ref.klass.name.underscore,
141
+ reference_type: ref.belongs_to? ? 'belongs_to' : 'has_one',
142
+ foreign_key: ref.foreign_key,
143
+ polymorphic: ref.polymorphic? || is_attachment
144
+ },
87
145
  virtual: false
88
146
  }
89
- end
147
+ end.compact
90
148
  end
91
149
 
92
150
  def fetch_associations(model)
93
151
  model.reflections.map do |name, ref|
94
- next if ref.polymorphic? && !ref.belongs_to?
152
+ next if ref.has_one? || ref.belongs_to?
95
153
 
96
154
  begin
97
155
  ref.klass
@@ -108,8 +166,6 @@ module Motor
108
166
  display_name: name.humanize,
109
167
  slug: name.underscore,
110
168
  model_name: model_class.name.underscore,
111
- model_slug: Utils.slugify(model_class),
112
- association_type: fetch_association_type(ref),
113
169
  foreign_key: ref.foreign_key,
114
170
  polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
115
171
  visible: true
@@ -117,21 +173,6 @@ module Motor
117
173
  end.compact
118
174
  end
119
175
 
120
- def fetch_association_type(association)
121
- case association.association_class.to_s
122
- when 'ActiveRecord::Associations::HasManyAssociation',
123
- 'ActiveRecord::Associations::HasManyThroughAssociation'
124
- 'has_many'
125
- when 'ActiveRecord::Associations::HasOneAssociation',
126
- 'ActiveRecord::Associations::HasOneThroughAssociation'
127
- 'has_one'
128
- when 'ActiveRecord::Associations::BelongsToAssociation'
129
- 'belongs_to'
130
- else
131
- raise ArgumentError, 'Unknown association type'
132
- end
133
- end
134
-
135
176
  def fetch_validators(model, column_name)
136
177
  model.validators_on(column_name).map do |validator|
137
178
  case validator
@@ -13,6 +13,7 @@ module Motor
13
13
  COLUMN_DEFAULTS = {
14
14
  access_type: 'read_write',
15
15
  default_value: nil,
16
+ reference: nil,
16
17
  validators: []
17
18
  }.with_indifferent_access
18
19
 
@@ -2,6 +2,18 @@
2
2
 
3
3
  module Motor
4
4
  module ReorderSchema
5
+ COLUMNS_DEFAULT_ORDER_WEIGHTS = {
6
+ id: 0,
7
+ updated_at: 2,
8
+ edited_at: 2,
9
+ created_at: 3,
10
+ inserted_at: 3,
11
+ deleted_at: 4,
12
+ archived_at: 4
13
+ }.with_indifferent_access
14
+
15
+ COLUMNS_DEFAULT_ORDER_WEIGHT = 1
16
+
5
17
  module_function
6
18
 
7
19
  # @param schema [Array<HashWithIndifferentAccess>]
@@ -19,7 +31,7 @@ module Motor
19
31
  scopes_order = configs["resources.#{model[:name]}.scopes.order"]
20
32
 
21
33
  model.merge(
22
- columns: sort_by_name(model[:columns], columns_order, sort_alphabetically: false),
34
+ columns: sort_by_name(sort_columns(model[:columns]), columns_order, sort_alphabetically: false),
23
35
  associations: sort_by_name(model[:associations], associations_order),
24
36
  actions: sort_by_name(model[:actions], actions_order, sort_alphabetically: false),
25
37
  tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false),
@@ -44,6 +56,14 @@ module Motor
44
56
  end
45
57
  end
46
58
 
59
+ def sort_columns(columns)
60
+ columns.each_with_object([]) do |column, acc|
61
+ weight = COLUMNS_DEFAULT_ORDER_WEIGHTS.fetch(column[:name], COLUMNS_DEFAULT_ORDER_WEIGHT)
62
+
63
+ (acc[weight] ||= []) << column
64
+ end.flatten.compact
65
+ end
66
+
47
67
  # @return [Hash<String, HashWithIndifferentAccess>]
48
68
  def load_configs
49
69
  Motor::Config.all.each_with_object({}) do |config, acc|
@@ -57,11 +57,16 @@ module Motor
57
57
  "$#{index}"
58
58
  end
59
59
 
60
- [
61
- format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit),
62
- 'SQL',
63
- variables.map { |variable_name, default_value| variables_hash[variable_name] || default_value }
64
- ]
60
+ attributes =
61
+ variables.map do |variable_name, default_value|
62
+ ActiveRecord::Relation::QueryAttribute.new(
63
+ variable_name,
64
+ variables_hash[variable_name] || default_value,
65
+ ActiveRecord::Type::Value.new
66
+ )
67
+ end
68
+
69
+ [format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit), 'SQL', attributes]
65
70
  end
66
71
  end
67
72
  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.13'
4
+ VERSION = '0.1.14'
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-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"
8
+ "main-f7c7f445c53544d2d7b8.css.gz": "main-f7c7f445c53544d2d7b8.css.gz",
9
+ "main-f7c7f445c53544d2d7b8.js.LICENSE.txt": "main-f7c7f445c53544d2d7b8.js.LICENSE.txt",
10
+ "main-f7c7f445c53544d2d7b8.js.gz": "main-f7c7f445c53544d2d7b8.js.gz",
11
+ "main.css": "main-f7c7f445c53544d2d7b8.css",
12
+ "main.js": "main-f7c7f445c53544d2d7b8.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.13
4
+ version: 0.1.14
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-29 00:00:00.000000000 Z
11
+ date: 2021-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -213,8 +213,8 @@ files:
213
213
  - lib/motor/ui_configs.rb
214
214
  - lib/motor/version.rb
215
215
  - ui/dist/fonts/ionicons.woff2
216
- - ui/dist/main-8f36a2746422efd1e8a8.css.gz
217
- - ui/dist/main-8f36a2746422efd1e8a8.js.gz
216
+ - ui/dist/main-f7c7f445c53544d2d7b8.css.gz
217
+ - ui/dist/main-f7c7f445c53544d2d7b8.js.gz
218
218
  - ui/dist/manifest.json
219
219
  homepage:
220
220
  licenses: